This is a first pass exploration of different aspects of beer.


Workflow Overview

Short Aside

The question of what should be a predictor variable for style is a bit murky here. What should be fair game for predicting style and what shouldn’t? Characteristics of a beer that are defined by its style would seem to be “cheating” in a way.

This document compiled by querying the beer database I built, specifically by sourcing the file read_from_db.R. This is done for expediency’s sake, (the code below detailing how to get the beer, run in full in run_it.R, takes some time to execute).

Get and Prepare Data

Getting beer, the age-old dilemma

base_url <- "http://api.brewerydb.com/v2"
key_preface <- "/?key="

paginated_request <- function(ep, addition) {    
  full_request <- NULL
  first_page <- fromJSON(paste0(base_url, "/", ep, "/", key_preface, key
                                , "&p=1"))
  number_of_pages <- ifelse(!(is.null(first_page$numberOfPages)), 
                            first_page$numberOfPages, 1)      

    for (page in 1:number_of_pages) {                               
    this_request <- fromJSON(paste0(base_url, "/", ep, "/", key_preface, key
                                    , "&p=", page, addition),
                             flatten = TRUE) 
    this_req_unnested <- unnest_it(this_request)    #  <- request unnested here
    print(this_req_unnested$currentPage)
    full_request <- bind_rows(full_request, this_req_unnested[["data"]])
  }
  full_request
} 

all_beer_raw <- paginated_request("beers", "&withIngredients=Y")
unnest_it <- function(df) {
  unnested <- df
  for(col in seq_along(df[["data"]])) {
    if(! is.null(ncol(df[["data"]][[col]]))) {
      if(! is.null(df[["data"]][[col]][["name"]])) {
        unnested[["data"]][[col]] <- df[["data"]][[col]][["name"]]
      } else {
        unnested[["data"]][[col]] <- df[["data"]][[col]][[1]]
      }
    }
  }
  unnested
}

Collapse Styles

collapse_styles <- function(df) {
  keywords <- c("Lager", "Pale Ale", "India Pale Ale", "Double India Pale Ale", "India Pale Lager", "Hefeweizen", "Barrel-Aged","Wheat", "Pilsner", "Pilsener", "Amber", "Golden", "Blonde", "Brown", "Black", "Stout", "Porter", "Red", "Sour", "Kölsch", "Tripel", "Bitter", "Saison", "Strong Ale", "Barley Wine", "Dubbel", "Altbier")
  
  for (beer in 1:nrow(df)) {
    if (grepl(paste(keywords, collapse="|"), df$style[beer])) {    
      for (keyword in keywords) {         
        if(grepl(keyword, df$style[beer]) == TRUE) {
          df$style_collapsed[beer] <- keyword    
        }                         
      } 
    } else {
      df$style_collapsed[beer] <- as.character(df$style[beer])       
    }
    print(df$style_collapsed[beer])
  }
  return(df)
}
collapse_further <- function(df) {
  df[["style_collapsed"]] <- df[["style_collapsed"]] %>%
    fct_collapse(
      "Wheat" = c("Hefeweizen", "Wheat"),
      "Pilsener" = c("Pilsner", "American-Style Pilsener") # pilsener == pilsner == pils
    )
  return(df)
}

Split out Ingredients

split_ingredients <- function(df, ingredients_to_split) {
  
  ncol_df <- ncol(df)
  
  for (ingredient in ingredients_to_split) {

    ingredient_split <- str_split(df[[ingredient]], ", ")    
    num_new_cols <- max(lengths(ingredient_split))    
  
    for (num in 1:num_new_cols) {
      
      this_col <- ncol_df + 1         
      
      df[, this_col] <- NA
      names(df)[this_col] <- paste0(ingredient, "_", num)
      ncol_df <- ncol(df)             
      for (row in seq_along(ingredient_split)) {          
        if (!is.null(ingredient_split[[row]][num])) {        
          df[row, this_col] <- ingredient_split[[row]][num]
        }
      }
      df[[names(df)[this_col]]] <- factor(df[[names(df)[this_col]]])
    }
    
    ncol_df <- ncol(df)
  }
  return(df)
}

Find the Most Popualar Styles

library(forcats)

# Pare down to only cases where style is not NA
beer_dat <- beer_necessities

beer_dat_pared <- beer_dat[complete.cases(beer_dat$style), ]

# Arrange beer dat by style popularity
style_popularity <- beer_dat_pared %>% 
  group_by(style) %>% 
  count() %>% 
  arrange(desc(n))
style_popularity

# Add a column that scales popularity
style_popularity <- bind_cols(style_popularity, 
                               n_scaled = as.vector(scale(style_popularity$n)))

# Find styles that are above a z-score of 0
popular_styles <- style_popularity %>% 
  filter(n_scaled > 0)

# Pare dat down to only beers that fall into those styles
popular_beer_dat <- beer_dat_pared %>% 
  filter(
    style %in% popular_styles$style
  ) %>% 
  droplevels() %>% 
  as_tibble() 
nrow(popular_beer_dat)

# Find the centers (mean abv, ibu, srm) of the most popular styles
style_centers <- popular_beer_dat %>% 
  group_by(style_collapsed) %>% 
  add_count() %>% 
  summarise(
    mean_abv = mean(abv, na.rm = TRUE),
    mean_ibu = mean(ibu, na.rm = TRUE), 
    mean_srm = mean(srm, na.rm = TRUE),
    n = median(n, na.rm = TRUE)          # Median here only for summarise. Should be just the same as n
  ) %>% 
  arrange(desc(n)) %>% 
  drop_na() %>% 
  droplevels()

# Give some nicer names
style_centers_rename <- style_centers %>% 
  rename(
    `Collapsed Style` = style_collapsed,
    `Mean ABV` = mean_abv,
    `Mean IBU` = mean_ibu,
    `Mean SRM` = mean_srm,
    `Numer of Beers` = n
  )

Take a look at the table

kable(style_centers_rename)
Collapsed Style Mean ABV Mean IBU Mean SRM Numer of Beers
India Pale Ale 6.578468 66.04268 9.989313 6524
Pale Ale 5.695480 40.86930 8.890306 4280
Stout 7.991841 43.89729 36.300000 4238
Wheat 5.158040 17.47168 5.861842 3349
Double India Pale Ale 8.930599 93.48142 11.006873 2525
Red 5.742565 33.81127 16.178862 2521
Lager 5.453718 30.64361 8.457447 2230
Saison 6.400189 27.25114 7.053476 2167
Blonde 5.595298 22.39432 5.625000 2044
Porter 6.182049 33.25369 32.197605 1973
Brown 6.159212 32.21577 23.592000 1462
Pilsener 5.227593 33.51346 4.413462 1268
Specialty Beer 6.446402 33.77676 15.520548 1044
Bitter 5.322364 38.28175 12.460526 939
Fruit Beer 5.195222 19.24049 8.666667 905
Herb and Spice Beer 6.621446 27.77342 18.166667 872
Sour 6.224316 18.88869 10.040816 797
Strong Ale 8.826425 36.74233 22.547945 767
Tripel 9.029775 32.51500 7.680556 734
Black 6.958714 65.50831 31.080000 622
Barley Wine 10.781600 74.04843 19.561404 605
Kölsch 4.982216 23.37183 4.371795 593
Barrel-Aged 9.002506 39.15789 18.133333 540
Other Belgian-Style Ales 7.516318 37.55812 17.549020 506
Pumpkin Beer 6.712839 23.48359 17.918033 458
Dubbel 7.509088 25.05128 22.940000 399
Scotch Ale 7.620233 26.36909 24.222222 393
German-Style Doppelbock 8.045762 28.88692 25.696970 376
Fruit Cider 6.205786 25.60000 12.000000 370
German-Style Märzen 5.746102 25.63796 14.322581 370

Now that the munging is done, onto the main question: do natural clusters in beer align with style boundaries?


Ingredients

To get more granular with ingredients, we can split out each individual ingredient into its own column. If a beer or style contains that ingredient, its row gets a 1 in that ingredient column and a 0 otherwise.

From this, we can find the total number of hops and malts per grouper.

pick_ingredient_get_beer <- function (ingredient_want, df, grouper) {
  
  # ----------------------- Setup --------------------------- #
  # We've already split ingredient number names out from the concatenated string into columns like `malt_name_1`,
  # `malt_name_2`, etc. We need to find the range of these columns; there will be a different number of malt
  # columns than hops columns, for instance. The first one will be `<ingredient>_name_1` and from this we can find
  # the index of this column in our dataframe. We get the name of last one with the `get_last_ing_name_col()`
  # function. Then we save a vector of all the ingredient column names in `ingredient_colnames`. It will stay
  # constant even if the indices change when we select out certain columns. 
  
  # First ingredient
  first_ingredient_name <- paste(ingredient_want, "_name_1", sep="")
  first_ingredient_index <- which(colnames(df)==first_ingredient_name)
  
  # Get the last ingredient
  get_last_ing_name_col <- function(df) {
    for (col in names(df)) {
      if (grepl(paste(ingredient_want, "_name_", sep = ""), col) == TRUE) {
        name_last_ing_col <- col
      }
    }
    return(name_last_ing_col)
  }
  
  # Last ingredient
  last_ingredient_name <- get_last_ing_name_col(df)
  last_ingredient_index <- which(colnames(df)==last_ingredient_name)
  
  # Vector of all the ingredient column names
  ingredient_colnames <- names(df)[first_ingredient_index:last_ingredient_index]
  
  # Non-ingredient column names we want to keep
  to_keep_col_names <- c("cluster_assignment", "name", "abv", "ibu", "srm", "style", "style_collapsed")
  
  # -------------------------------------------------------------------------------# 
  
  # Inside `gather_ingredients()` we take out superflous column names that are not in `to_keep_col_names` or one 
  # of the ingredient columns, find what the new ingredient column indices are, since they'll have changed after 
  # we pared down and then gather all of the ingredient columns (e.g., `hops_name_1`) into one long column, 
  # `ing_keys` and all the actual ingredient names (e.g., Cascade) into `ing_names`.
  
  # ----------------------------- Gather columns --------------------------------- #
  gather_ingredients <- function(df, cols_to_gather) {
    to_keep_indices <- which(colnames(df) %in% to_keep_col_names)
    
    selected_df <- df[, c(to_keep_indices, first_ingredient_index:last_ingredient_index)]
    
    new_ing_indices <- which(colnames(selected_df) %in% cols_to_gather)    # indices will have changed since we pared down 
    
    df_gathered <- selected_df %>%
      gather_(
        key_col = "ing_keys",
        value_col = "ing_names",
        gather_cols = colnames(selected_df)[new_ing_indices]
      ) %>%
      mutate(
        count = 1
      )
    df_gathered
  }
  beer_gathered <- gather_ingredients(df, ingredient_colnames)  # ingredient colnames defined above function
  # ------------------------------------------------------------------------------- # 
  
  # Next we get a vector of all ingredient levels and take out the one that's an empty string and 
  # use this vector of ingredient levels in `select_spread_cols()` below
  # Get a vector of all ingredient levels
  beer_gathered$ing_names <- factor(beer_gathered$ing_names)
  ingredient_levels <- levels(beer_gathered$ing_names) 
  
  # Take out the level that's just an empty string
  to_keep_levels <- !(c(1:length(ingredient_levels)) %in% which(ingredient_levels == ""))
  ingredient_levels <- ingredient_levels[to_keep_levels]
  
  beer_gathered$ing_names <- as.character(beer_gathered$ing_names)
  
  # ----------------------------------------------------------------------------- # 
  
  # Then we spread the ingredient names: we take what was previously the `value` in our gathered dataframe, the
  # actual ingredient names (Cascade, Centennial) and make that our `key`; it'll form the new column names. The
  # new `value` is `value` is count; it'll populate the row cells. If a given row has a certain ingredient, it
  # gets a 1 in the corresponding cell, an NA otherwise. 
  # We add a unique idenfitier for each row with `row`, which we'll drop later (see [Hadley's SO
  # comment](https://stackoverflow.com/questions/25960394/unexpected-behavior-with-tidyr)).
  
  # ------------------------------- Spread columns -------------------------------- #
  spread_ingredients <- function(df) {
    df_spread <- df %>% 
      mutate(
        row = 1:nrow(df)        # Add a unique idenfitier for each row which we'll need in order to spread; we'll drop this later
      ) %>%                                 
      spread(
        key = ing_names,
        value = count
      ) 
    return(df_spread)
  }
  beer_spread <- spread_ingredients(beer_gathered)
  # ------------------------------------------------------------------------------- # 
  
  # ------------------------- Select only certain columns ------------------------- #
  select_spread_cols <- function(df) {
    to_keep_col_indices <- which(colnames(df) %in% to_keep_col_names)
    to_keep_ingredient_indices <- which(colnames(df) %in% ingredient_levels)
    
    to_keep_inds_all <- c(to_keep_col_indices, to_keep_ingredient_indices)
    
    new_df <- df %>% 
      select_(
        .dots = to_keep_inds_all
      )
    return(new_df)
  }
  beer_spread_selected <- select_spread_cols(beer_spread)
  # ------------------------------------------------------------------------------- # 
  # Take out all rows that have no ingredients specified at all
  inds_to_remove <- apply(beer_spread_selected[, first_ingredient_index:last_ingredient_index], 
                          1, function(x) all(is.na(x)))
  beer_spread_no_na <- beer_spread_selected[ !inds_to_remove, ]
  
  
  # ----------------- Group ingredients by the grouper specified ------------------- #
  # Then we do the final step and group by the groupers.
  
  get_ingredients_per_grouper <- function(df, grouper = grouper) {
    df_grouped <- df %>%
      ungroup() %>% 
      group_by_(grouper)
    
    not_for_summing <- which(colnames(df_grouped) %in% to_keep_col_names)
    max_not_for_summing <- max(not_for_summing)
    
    per_grouper <- df_grouped %>% 
      select(-c(abv, ibu, srm)) %>%    # taking out temporarily
      summarise_if(
        is.numeric,              
        sum, na.rm = TRUE
        # -c(abv, ibu, srm)
      ) %>%
      mutate(
        total = rowSums(.[(max_not_for_summing + 1):ncol(.)], na.rm = TRUE)    
      )
    
    # Send total to the second position
    per_grouper <- per_grouper %>% 
      select(
        name, total, everything()
      )
    
    # Replace total column with more descriptive name: total_<ingredient>
    names(per_grouper)[which(names(per_grouper) == "total")] <- paste0("total_", ingredient_want)
    
    return(per_grouper)
  }
  # ------------------------------------------------------------------------------- # 
  
  ingredients_per_grouper <- get_ingredients_per_grouper(beer_spread_selected, grouper)
  return(ingredients_per_grouper)
}
# Run the entire function with ingredient_want set to hops, grouping by name
ingredients_per_beer_hops <- pick_ingredient_get_beer(ingredient_want = "hops", 
                                                      beer_necessities, 
                                                      grouper = c("name", "style_collapsed"))
# Same for malt
ingredients_per_beer_malt <- pick_ingredient_get_beer(ingredient_want = "malt", 
                                                      beer_necessities, 
                                                      grouper = c("name", "style_collapsed"))
# Join those on our original dataframe by name
beer_ingredients_join_first_ingredient <- left_join(beer_necessities, ingredients_per_beer_hops,
                                                    by = "name")
beer_ingredients_join <- left_join(beer_ingredients_join_first_ingredient, ingredients_per_beer_malt,
                                   by = "name")
# Take out some unnecessary columns
unnecessary_cols <- c("styleId", "abv_scaled", "ibu_scaled", "srm_scaled", 
                      "hops_id", "malt_id", "glasswareId", "style.categoryId")
beer_ingredients_join <- beer_ingredients_join[, (! names(beer_ingredients_join) %in% unnecessary_cols)]
# If we also want to take out any of the malt_name_1, malt_name_2, etc. columns we can do this with a grep
more_unnecessary <- c("hops_name_|malt_name_")
beer_ingredients_join <- 
  beer_ingredients_join[, (! grepl(more_unnecessary, names(beer_ingredients_join)) == TRUE)]
# Reorder columns a bit
beer_ingredients_join <- beer_ingredients_join %>% 
  select(
    id, name, total_hops, total_malt, everything(), -description
  )
# And get a df that includes total_hops and total_malt but not all the other ingredient columns
beer_totals <- beer_ingredients_join %>% 
  select(
    id, name, total_hops, total_malt, style, style_collapsed,
    abv, ibu, srm, glass, hops_name, malt_name
  )

Now we’re left with something of a sparse matrix of all the ingredients compared to all the beers

kable(beer_ingredients_join[1:20, ])

Unsupervised Clustering

We K-means cluster beers based on certain numeric predictor variables.

Prep

library(NbClust)
cluster_it <- function(df, preds, to_scale, resp, n_centers) {
  df_for_clustering <- df %>%
    select_(.dots = c(response_vars, cluster_on)) %>%
    na.omit() %>%
    filter(
      abv < 20 & abv > 3
    ) %>%
    filter(
      ibu < 200
    )
  df_all_preds <- df_for_clustering %>%
    select_(.dots = preds)
  df_preds_scale <- df_all_preds %>%
    select_(.dots = to_scale) %>%
    rename(
      abv_scaled = abv,
      ibu_scaled = ibu,
      srm_scaled = srm
    ) %>%
    scale() %>%
    as_tibble()
  df_preds <- bind_cols(df_preds_scale, df_all_preds[, (!names(df_all_preds) %in% to_scale)])
  df_outcome <- df_for_clustering %>%
    select_(.dots = resp) %>%
    na.omit()
  set.seed(9)
  clustered_df_out <- kmeans(x = df_preds, centers = n_centers, trace = TRUE)
  clustered_df <- as_tibble(data.frame(
    cluster_assignment = factor(clustered_df_out$cluster),
    df_outcome, df_preds,
    df_for_clustering %>% select(abv, ibu, srm)))
  return(clustered_df)
}

Cluster

First we’ll run the fuction with 10 centers, and cluster on the predictors ABV, IBU, SRM, total_hops, and total_malt.

cluster_on <- c("abv", "ibu", "srm", "total_hops", "total_malt")
to_scale <- c("abv", "ibu", "srm", "total_hops", "total_malt")
response_vars <- c("name", "style", "style_collapsed")
clustered_beer <- cluster_it(df = beer_totals,
                             preds = cluster_on,
                             to_scale = to_scale,
                             resp = response_vars,
                             n_centers = 10)
KMNS(*, k=10): iter=  1, indx=5
 QTRAN(): istep=4472, icoun=1
 QTRAN(): istep=8944, icoun=84
 QTRAN(): istep=13416, icoun=31
 QTRAN(): istep=17888, icoun=310
 QTRAN(): istep=22360, icoun=444
 QTRAN(): istep=26832, icoun=3571
KMNS(*, k=10): iter=  2, indx=28
 QTRAN(): istep=4472, icoun=3
 QTRAN(): istep=8944, icoun=26
 QTRAN(): istep=13416, icoun=0
 QTRAN(): istep=17888, icoun=45
 QTRAN(): istep=22360, icoun=37
 QTRAN(): istep=26832, icoun=170
 QTRAN(): istep=31304, icoun=45
 QTRAN(): istep=35776, icoun=64
 QTRAN(): istep=40248, icoun=692
KMNS(*, k=10): iter=  3, indx=6
 QTRAN(): istep=4472, icoun=16
 QTRAN(): istep=8944, icoun=36
 QTRAN(): istep=13416, icoun=318
 QTRAN(): istep=17888, icoun=1114
 QTRAN(): istep=22360, icoun=1628
 QTRAN(): istep=26832, icoun=1518
KMNS(*, k=10): iter=  4, indx=227
 QTRAN(): istep=4472, icoun=560
 QTRAN(): istep=8944, icoun=1426
 QTRAN(): istep=13416, icoun=401
KMNS(*, k=10): iter=  5, indx=4472

Head of the clustering data

kable(clustered_beer[1:20, ])

A table of cluster counts broken down by style

cluster_table_counts <- table(style = clustered_beer$style_collapsed, cluster = clustered_beer$cluster_assignment)

kable(cluster_table_counts)

Plot the clusters. There are 3 axes: ABV, IBU, and SRM, so we choose two at a time.

clustered_beer_plot_abv_ibu <- ggplot(data = clustered_beer, aes(x = abv, y = ibu, colour = cluster_assignment)) + 
  geom_jitter() + theme_minimal()  +
  ggtitle("k-Means Clustering of Beer by ABV, IBU, SRM") +
  labs(x = "ABV", y = "IBU") +
  labs(colour = "Cluster Assignment")
clustered_beer_plot_abv_ibu

clustered_beer_plot_abv_srm <- ggplot(data = clustered_beer, aes(x = abv, y = srm, colour = cluster_assignment)) + 
  geom_jitter() + theme_minimal()  +
  ggtitle("k-Means Clustering of Beer by ABV, IBU, SRM") +
  labs(x = "ABV", y = "SRM") +
  labs(colour = "Cluster Assignment")
clustered_beer_plot_abv_srm

Now we can add in the style centers (means) for each style_collapsed and label it.

library(ggrepel)
abv_ibu_clusters_vs_style_centers <- ggplot() +   
  geom_point(data = clustered_beer, 
             aes(x = abv, y = ibu, colour = cluster_assignment), alpha = 0.5) +
  geom_point(data = style_centers,
             aes(mean_abv, mean_ibu), colour = "black") +
  geom_text_repel(data = style_centers, aes(mean_abv, mean_ibu, label = style_collapsed), 
                  box.padding = unit(0.45, "lines"),
                  family = "Calibri",
                  label.size = 0.3) +
  ggtitle("Popular Styles vs. k-Means Clustering of Beer by ABV, IBU, SRM") +
  labs(x = "ABV", y = "IBU") +
  labs(colour = "Cluster Assignment") +
  theme_bw()
abv_ibu_clusters_vs_style_centers

The clustering above used a smaller number of clusters (10) than there are styles_collapsed. That makes it difficult to determine whether a given style fits snugly into a cluster or not.

Cluster on just certain selected styles

We’ll take five very distinct collapsed styles and re-run the clustering on beers that fall into these categories. These styles were intentionally chosen because they are quite distinct: Blonde, IPA, Stout, Tripel, Wheat. Arguably, of these five styles Blondes and Wheats are the closest

styles_to_keep <- c("Blonde", "India Pale Ale", "Stout", "Tripel", "Wheat")
bt_certain_styles <- beer_totals %>%
  filter(
    style_collapsed %in% styles_to_keep
  )
cluster_on <- c("abv", "ibu", "srm", "total_hops", "total_malt")
to_scale <- c("abv", "ibu", "srm", "total_hops", "total_malt")
response_vars <- c("name", "style", "style_collapsed")
certain_styles_clustered <- cluster_it(df = bt_certain_styles,
                                 preds = cluster_on,
                                 to_scale = to_scale,
                                 resp = response_vars,
                                 n_centers = 5)
KMNS(*, k=5): iter=  1, indx=4
 QTRAN(): istep=1322, icoun=2
 QTRAN(): istep=2644, icoun=20
 QTRAN(): istep=3966, icoun=96
 QTRAN(): istep=5288, icoun=0
 QTRAN(): istep=6610, icoun=13
 QTRAN(): istep=7932, icoun=285
KMNS(*, k=5): iter=  2, indx=13
 QTRAN(): istep=1322, icoun=147
 QTRAN(): istep=2644, icoun=138
 QTRAN(): istep=3966, icoun=20
 QTRAN(): istep=5288, icoun=599
KMNS(*, k=5): iter=  3, indx=20
 QTRAN(): istep=1322, icoun=146
 QTRAN(): istep=2644, icoun=617
KMNS(*, k=5): iter=  4, indx=1322
style_centers_certain_styles <- style_centers %>% 
  filter(style_collapsed %in% styles_to_keep)

Table of style vs. cluster.

kable(table(style = certain_styles_clustered$style_collapsed, cluster = certain_styles_clustered$cluster_assignment))

Now that we have a manageable number of styles, we can see how well fit each cluster is to each style. If the features we clustered on perfectly predicted style, there would each color (cluster) would be unique to each facet of the plot. (E.g., left entirely blue, second from left entirely green, etc.)

by_style_plot <- ggplot() +   
  geom_point(data = certain_styles_clustered, 
             aes(x = abv, y = ibu,
                 colour = cluster_assignment), alpha = 0.5) +
  facet_grid(. ~ style_collapsed) +
  geom_point(data = style_centers_certain_styles,
           aes(mean_abv, mean_ibu), colour = "black", shape = 5) +
  ggtitle("Selected Styles Cluster Assignment") +
  labs(x = "ABV", y = "IBU") +
  labs(colour = "Cluster") +
  theme_bw()
by_style_plot

–>

Random asides into hops

Do more hops always mean more bitterness?


ggplot(data = beer_ingredients_join, aes(total_hops, ibu)) +
  geom_point(aes(total_hops, ibu, colour = style_collapsed)) +
  geom_smooth(method = lm, se = FALSE, colour = "black") + 
  ggtitle("Hops Per Beer vs. Bitterness") +
  labs(x = "Number of Hops", y = "IBU", colour = "Style Collapsed") +
  theme_minimal()

hops_ibu_lm <- lm(ibu ~ total_hops, data = beer_ingredients_join)
summary(hops_ibu_lm)
ggplot(data = beer_ingredients_join[which(beer_ingredients_join$total_hops > 2
                                          & beer_ingredients_join$total_hops < 8), ], aes(total_hops, ibu)) +
  geom_point(aes(total_hops, ibu, colour = style_collapsed)) +
  geom_smooth(method = lm, se = FALSE, colour = "black") +
  ggtitle("3+ Hops Per Beer vs. Bitterness") +
  labs(x = "Number of Hops", y = "IBU", colour = "Style Collapsed") +
  theme_minimal()

Most popular hops

# Gather up all the hops columns into one called `hop_name`
beer_necessities_hops_gathered <- beer_necessities %>%
  gather(
    hop_key, hop_name, hops_name_1:hops_name_13
  ) %>% as_tibble()

# Filter to just those beers that have at least one hop
beer_necessities_w_hops <- beer_necessities_hops_gathered %>% 
  filter(!is.na(hop_name)) %>% 
  filter(!hop_name == "")

beer_necessities_w_hops$hop_name <- factor(beer_necessities_w_hops$hop_name)

# For all hops, find the number of beers they're in as well as those beers' mean IBU and ABV
hops_beer_stats <- beer_necessities_w_hops %>% 
  ungroup() %>% 
  group_by(hop_name) %>% 
  summarise(
    mean_ibu = mean(ibu, na.rm = TRUE), 
    mean_abv = mean(abv, na.rm = TRUE),
    n = n()
  )

# Pare to hops that are used in at least 50 beers
pop_hops_beer_stats <- hops_beer_stats[hops_beer_stats$n > 50, ]
kable(pop_hops_beer_stats)

# Keep just beers that contain these most popular hops
beer_necessities_w_popular_hops <- beer_necessities_w_hops %>% 
  filter(hop_name %in% pop_hops_beer_stats$hop_name) %>% 
  droplevels() 

Are there certian hops that are used more often in very high IBU or ABV beers? Hard to detect a pattern

ggplot(data = beer_necessities_w_popular_hops) + 
  geom_point(aes(abv, ibu, colour = hop_name)) +
  ggtitle("Beers Containing most Popular Hops") +
  labs(x = "ABV", y = "IBU", colour = "Hop Name") +
  theme_minimal()
ggplot(data = pop_hops_beer_stats) + 
  geom_point(aes(mean_abv, mean_ibu, colour = hop_name, size = n)) +
  ggtitle("Most Popular Hops' Effect on Alcohol and Bitterness") +
  labs(x = "Mean ABV per Hop Type", y = "Mean IBU per Hop Type", colour = "Hop Name", 
       size = "Number of Beers") +
  theme_minimal()

Neural Net

library(nnet)
library(caret)
run_neural_net <- function(df, outcome, predictor_vars) {
  out <- list(outcome = outcome)
  
  # Create a new column outcome; it's style_collapsed if you set outcome to style_collapsed, and style otherwise
  if (outcome == "style_collapsed") {
    df[["outcome"]] <- df[["style_collapsed"]]
  } else {
    df[["outcome"]] <- df[["style"]]
  }
  df$outcome <- factor(df$outcome)
  
  cols_to_keep <- c("outcome", predictor_vars)
  
  df <- df %>%
    select_(.dots = cols_to_keep) %>%
    mutate(row = 1:nrow(df)) %>% 
    droplevels()
  # Select 80% of the data for training
  df_train <- sample_n(df, nrow(df)*(0.8))
  
  # The rest is for testing
  df_test <- df %>%
    filter(! (row %in% df_train$row)) %>%
    select(-row)
  
  df_train <- df_train %>%
    select(-row)
  
  # Build multinomail neural net
  nn <- multinom(outcome ~ .,
                 data = df_train, maxit=500, trace=T)
  # Which variables are the most important in the neural net?
  most_important_vars <- varImp(nn)
  # How accurate is the model? Compare predictions to outcomes from test data
  nn_preds <- predict(nn, type="class", newdata = df_test)
  nn_accuracy <- postResample(df_test$outcome, nn_preds)
  out <- list(out, nn = nn, most_important_vars = most_important_vars,
              df_test = df_test,
              nn_preds = nn_preds,
           nn_accuracy = nn_accuracy)
  return(out)
}
p_vars <- c("total_hops", "total_malt", "abv", "ibu", "srm", "glass")
nn_collapsed_out <- run_neural_net(df = beer_totals, outcome = "style_collapsed", 
                         predictor_vars = p_vars)
Error in nnet.default(X, Y, w, mask = mask, size = 0, skip = TRUE, softmax = TRUE,  : 
  too many (1422) weights

nn_notcollapsed_out <- run_neural_net(df = beer_totals, outcome = "style", 
                         predictor_vars = p_vars)

nn_notcollapsed_out$nn_accuracy

nn_notcollapsed_out$most_important_vars

And now if we add glass as a predictor?


p_vars_add_glass <- c("total_hops", "total_malt", "abv", "ibu", "srm")

nn_collapsed_out_add_glass <- run_neural_net(df = beer_ingredients_join, outcome = "style_collapsed", 
                         predictor_vars = p_vars_no_glass)

nn_collapsed_out_add_glass$nn_accuracy

nn_collapsed_out_add_glass$most_important_vars

Random forest with all ingredients

  • We can use a random forest to get even more granular with ingredients
    • The sparse ingredient dataframe was too complex for the multinomial neural net; however, we can
  • Here we don’t include glass as a predictor

library(ranger)
library(stringr)

bi <- beer_ingredients_join %>% 
  select(-c(id, name, cluster_assignment, style, hops_name, malt_name,
            glass)) %>% 
  mutate(row = 1:nrow(.)) 

bi$style_collapsed <- factor(bi$style_collapsed)


# ranger complains about special characters and spaces in ingredient column names. Take them out and replace with empty string.
names(bi) <- tolower(names(bi))
names(bi) <- str_replace_all(names(bi), " ", "")
names(bi) <- str_replace_all(names(bi), "([\\(\\)-\\/')]+)", "")

# Keep 80% for training
bi_train <- sample_n(bi, nrow(bi)*(0.8))

# The rest is for testing
bi_test <- bi %>%
  filter(! (row %in% bi_train$row)) %>%
  dplyr::select(-row)

bi_train <- bi_train %>%
  dplyr::select(-row)


bi_rf <- ranger(style_collapsed ~ ., data = bi_train, importance = "impurity", seed = 11)

OOB (out of bag) prediction error is around 58% * This calculated from tree samples constructed but not used in training set; these trees become effectively part of test set

bi_rf

We can compare predicted classification on the test set to their actual style classification.

pred_bi_rf <- predict(bi_rf, dat = bi_test)
table(bi_test$style_collapsed, pred_bi_rf$predictions)

Variable importance * Interestingly, ABV, IBU, and SRM are all much more important in the random forest than total_hops and total_malt


importance(bi_rf)[1:10]

How does a CSRF (case-specific random forest) fare?


bi_csrf <- csrf(style_collapsed ~ ., training_data = bi_train, test_data = bi_test,
                params1 = list(num.trees = 5, mtry = 4),
                params2 = list(num.trees = 2))

csrf_acc <- postResample(bi_csrf, bi_test$style_collapsed)

csrf_acc

Final Thoughts

Style first, forgiveness later?

  • One reason seems that beers are generally brewed with style in mind first (“let’s make a pale ale”) rather than deciding the beer’s style after determining its characteristics and idiosyncrasies
    • Even if the beer turns out more like a sour, and in a blind taste test might be classified as a sour more often than a pale ale, it still gets the label pale ale
    • This makes the style definitions broader and harder to predict

Future Directions

  • Incorporate flavor profiles for beers sourced/scraped from somewhere
  • Implement a GAN to come up with beer names
  • More on the hops deep dive: which hops are used most often in which styles?
LS0tCnRpdGxlOiBEYXRhIFNjaWVuY2UgTXVzaW5ncyBvbiBCZWVyCmF1dGhvcjoKICBuYW1lOiBBbWFuZGEgRG9iYnluCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIiVCICVkLCAlWSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IGZhbHNlCiAgICB0aGVtZTogeWV0aQogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogZmFsc2UKICBnaXRodWJfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIAojIG91dHB1dDoKIyAgIGh0bWxfZG9jdW1lbnQ6CiMgICAgIGtlZXBfbWQ6IHRydWUKIyAgICAgdG9jOiBmYWxzZQojICAgICB0aGVtZTogeWV0aQojICAgZ2l0aHViX2RvY3VtZW50OgojICAgICB0b2M6IHRydWUKLS0tCgoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQpsaWJyYXJ5KGtuaXRyKQoKIyBrbml0cjo6b3B0c19rbml0JHNldChyb290LmRpcj1ub3JtYWxpemVQYXRoKCcuLi8nKSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTgpIApvcHRpb25zKGtuaXRyLnRhYmxlLmZvcm1hdCA9ICdtYXJrZG93bicpCgojIHNvdXJjZSgiLi9yZWFkX2Zyb21fZGIuUiIpCnNvdXJjZSgiLi9tb3N0X3BvcHVsYXJfc3R5bGVzLlIiKQojICMgc291cmNlKCIuL2NsdXN0ZXIuUiIpCiMgIyBzb3VyY2UoIi4vcGxvdC5SIikKIyBzb3VyY2UoIi4vZ2V0X2luZ3JlZGllbnRfbGV2ZWxzLlIiKQpgYGAKClRoaXMgaXMgYSBmaXJzdCBwYXNzIGV4cGxvcmF0aW9uIG9mIGRpZmZlcmVudCBhc3BlY3RzIG9mIGJlZXIuCgoqIERhdGEgY291cnRlc3kgb2YgW0JyZXdlcnlEQl0oaHR0cDovL3d3dy5icmV3ZXJ5ZGIuY29tL2RldmVsb3BlcnMpCiAgICAqIFNwZWNpYWwgdGhhbmtzIHRvIFtLcmlzIEtyb3NraV0oaHR0cHM6Ly9rcm8uc2tpLykgZm9yIGRhdGEgaWRlYXRpb24gYW5kIGJlZXIKICAgIAogICAgCioqKiAKCgoqIFRoZSBtYWluIHF1ZXN0aW9uIG9uIHRoZSB0YWJsZSBpcyB0aGlzOgogICAgKiBBcmUgYmVlciBzdHlsZXMgYWN0dWFsbHkgaW5kaWNhdGl2ZSBvZiBzaGFyZWQgYXR0cmlidXRlcyBvZiB0aGUgYmVlcnMgd2l0aGluIHRoYXQgc3R5bGU/IE9yIGFyZSBzdHlsZSBib3VuZGFyaWVzIG1vcmUgb3IgbGVzcyBhcmJpdHJhcnk/CiAgICAgICogVHdvIGFwcHJvYWNoZXM6IGNsdXN0ZXJpbmcgYW5kIHByZWRpY3Rpb24gCiAgICAgICogQ2x1c3RlcmluZzogYXJlIHRoZXJlIG5hdHVyYWwgY2x1c3RlcnMgYWNyb3NzIHRoZSBzcGVjdHVtIG9mIGJlZXJzIHRoYXQgYWxpZ24gd2VsbCB3aXRoIHRoZSBzdHlsZXMgdGhleSdyZSBncm91cGVkIGludG8/IAogICAgICAgICAgKiBVbnN1cGVydmlzZWQgKGstbWVhbnMpIGNsdXN0ZXJpbmcgYmFzZWQgb24gCiAgICAgICAgICAgICAgKiBBQlYgKGFsY29ob2wgYnkgdm9sdW1lKSwgSUJVIChpbnRlcm5hdGlvbmFsIGJpdHRlcm5lc3MgdW5pdHMpLCBTUk0gKG1lYXN1cmUgb2YgY29sb3IpCiAgICAgICAgICAgICAgKiBIb3cgd2VsbCBkbyB0aGVzZSBtYXRjaCB1cCB3aXRoIHZhcmlvdXMgInN0eWxlIGNlbnRlcnMsIiBkZWZpbmVkIGJ5IG1lYW4gb2YgQUJWLCBJQlUsIGFuZCBTUk0gcGVyIGJlZXIgc3R5bGUKICAgICAgKiBQcmVkaWN0aW9uOiBjYW4gd2UgcHJlZGljdCBhIGJlZXIncyBzdHlsZSBiYXNlZCBvbiBjZXJ0YWluIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgYmVlcj8KICAgICAgICAgICogTmV1cmFsIG5ldCAKICAgICAgICAgICogUmFuZG9tIGZvcmVzdAogICAgICAKKiBBbnN3ZXIgdGh1cyBmYXIKICAgICogQmVlci1pbnRyaW5zaWMgYXR0cmlidXRlcyBhcmVuJ3QgZ3JlYXQgcHJlZGljdG9ycyBvZiBzdHlsZQogICAgKiBTdHlsZS1kZWZpbmVkIGF0dHJpYnV0ZXMgYXJlIHRoZSBiZXN0IHByZWRpY3RvcnMKICAgICAgICAqIEZvciBpbnN0YW5jZSwgdGhlIGdsYXNzIGEgYmVlciBpcyBzZXJ2ZWQgaW4gKHdoaWNoIGlzIGRlZmluZWQgYnkgaXRzIHN0eWxlKSBpcyBhIG11Y2ggYmV0dGVyIHByZWRpY3RvciBvZiBpdHMgc3R5bGUgdGhhbiBhY3R1YWwgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBiZWVyIGxpa2UgQUJWIGFuZCBldmVuIHRoZSBudW1iZXIgb2YgZGlmZmVyZW50IHR5cGVzIG9mIGhvcHMgaXQgY29udGFpbnMKCiFbXSguL3RhcHMuanBnKQoKCgojIyMgV29ya2Zsb3cgT3ZlcnZpZXcKCiogSGl0IHRoZSBCcmV3ZXJ5REIgQVBJIHRvIGl0ZXJhdGl2ZWx5IHB1bGwgaW4gYWxsIGJlZXJzIGFuZCB0aGVpciBpbmdyZWRpZW50cyBhbG9uZyB3aXRoIG90aGVyIHRoaW5ncyB3ZSBtaWdodCB3YW50IGxpa2UgYnJld2VyaWVzIGFuZCBnbGFzc3dhcmUKKiBVbm5lc3QgdGhlIEpTT04gcmVzcG9uc2VzLCBpbmNsdWRpbmcgYWxsIHRoZSBpbmdyZWRpZW50cyBjb2x1bW5zLCBhbmQgCiogRHVtcCB0aGlzIGFsbCBpbnRvIGEgTXlTUUwgZGF0YWJhc2UgCgoqIENyZWF0ZSBhIGBzdHlsZV9jb2xsYXBzZWRgIGNvbHVtbiB0byByZWR1Y2UgdGhlIG51bWJlciBvZiBsZXZlbHMgb2Ygb3VyIG91dGNvbWUgdmFyaWFibGUKICAgICogYGdyZXBgIHRocm91Z2ggZWFjaCBiZWVyJ3Mgc3R5bGUgdG8gZGV0ZXJtaW5lIGlmIHRoYXQgc3R5bGUgY29udGFpbnMgYSBrZXl3b3JkIHRoYXQgcXVhbGlmaWVzIGl0IHRvIGJlIHJvbGxlZCBpbnRvIGEgY29sbGFwc2VkIHN0eWxlCiAgICAqIElmIGl0IGRvZXMsIGl0IGdldHMgdGhhdCBrZXl3b3JkIGluIGEgYHN0eWxlX2NvbGxhcHNlZGAgY29sdW1uIAogICAgKiBGdXJ0aGVyIGNvbGxwYXNlIHN0eWxlcyB0aGF0IGFyZSBzaW1pbGFyIGxpa2UgSGVmZXdlaXplbiBhbmQgV2l0IGludG8gV2hlYXQKICAgIAoqIFVubmVzdCB0aGUgaW5ncmVkaWVudHMgYGhvcHNgIGFuZCBgbWFsdHNgIGludG8gYSBzcGFyc2UgbWF0cml4CiAgICAqIEluZGl2aWR1YWwgaW5ncmVkaWVudHMgYXMgY29sdW1ucywgYmVlcnMgYXMgcm93czsgY2VsbCBnZXRzIGEgMSBpZiBpbmdyZWRpZW50IGlzIHByZXNlbnQgYW5kIDAgb3RoZXJ3aXNlIAogICAgCiogQ2x1c3RlcjogdW5zdXBlcnZpc2VkIGstbWVhbnMgY2xzdXRlcmluZyBiYXNlZCBvbiBBQlYsIElCVSwgYW5kIFNSTQoKKiBSdW4gYSBuZXVyYWwgbmV0CiAgICAqIFByZWRpY3QgZWl0aGVyIGBzdHlsZWAgb3IgYHN0eWxlX2NvbGxhcHNlZGAgZnJvbSBhbGwgdGhlIHByZWRpY3RvcnMgaW5jbHVkaW5nIHRoZSB0b3RhbCBudW1iZXIgb2YgaG9wcyBhbmQgbWFsdHMgcGVyIGJlZXIKCgoqKlNob3J0IEFzaWRlKioKClRoZSBxdWVzdGlvbiBvZiB3aGF0IHNob3VsZCBiZSBhIHByZWRpY3RvciB2YXJpYWJsZSBmb3Igc3R5bGUgaXMgYSBiaXQgbXVya3kgaGVyZS4gV2hhdCBzaG91bGQgYmUgZmFpciBnYW1lIGZvciBwcmVkaWN0aW5nIHN0eWxlIGFuZCB3aGF0IHNob3VsZG4ndD8gQ2hhcmFjdGVyaXN0aWNzIG9mIGEgYmVlciB0aGF0IGFyZSBkZWZpbmVkICpieSogaXRzIHN0eWxlIHdvdWxkIHNlZW0gdG8gYmUgImNoZWF0aW5nIiBpbiBhIHdheS4gCgoqIE1haW4gY2FuZGlkYXRlcyBhcmU6CiAgICAqIEFCViAoYWxjb2hvbCBieSB2b2x1bWUpLCBJQlUgKGludGVybmF0aW9uYWwgYml0dGVybmVzcyB1bml0cyksIFNSTSAoc3RhbmRhcmQgcmVmZXJlbmNlIG1lYXN1cmUsIGEgc2NhbGUgb2YgYmVlciBjb2xvciBmcm9tIGxpZ2h0IHRvIGRhcmspCiAgICAgICAgKiBUaGVzZSBhcmUgb3V0cHV0cyBvZiBhIGJlZXIgdGhhdCBtZWFuaW5nZnVsbHkgZGVmaW5lIHRoZSBiZWVyIGFuZCBhcmUgdGhlb3JldGljYWxseSBvcnRob2dvbmFsIHRvIGVhY2ggb3RoZXIKICAgICogSW5ncmVkaWVudHMgaW4gYSBiZWVyIHN1Y2ggYXMgaG9wcyBhbmQgbWFsdHMKICAgICAgICAqIElucHV0cyB0byBhIGJlZXIgdGhhdCBoYXZlIHNvbWUgZWZmZWN0IG9uIGl0cyBmbGF2b3IgcHJvZmlsZQogICAgICAgICogU2VtaS1jaGVhdGluZyBiZWNhdXNlIGlmIHN0eWxlIGlzIGRldGVybWluZWQgYmVmb3JlaGFuZCBpdCBsaWtlbHkgZGV0ZXJtaW5lcyBhdCBsZWFzdCBpbiBwYXJ0IHdoaWNoIGluZ3JlZGllbnRzIGFyZSBhZGRlZCAKICAgICogR2xhc3MgdHlwZQogICAgICAgICogVGhpcyBpcyBkZWZpbmVkIGVudGlyZWx5IGJ5IHN0eWxlIGFuZCBpcyB2ZXJ5IHByZWRpY3RpdmUgb2YgaXQKCgoKKipUaGlzIGRvY3VtZW50IGNvbXBpbGVkIGJ5IHF1ZXJ5aW5nIHRoZSBiZWVyIGRhdGFiYXNlIEkgYnVpbHQsIHNwZWNpZmljYWxseSBieSBzb3VyY2luZyB0aGUgZmlsZSByZWFkX2Zyb21fZGIuUi4gVGhpcyBpcyBkb25lIGZvciBleHBlZGllbmN5J3Mgc2FrZSwgKHRoZSBjb2RlIGJlbG93IGRldGFpbGluZyBob3cgdG8gZ2V0IHRoZSBiZWVyLCBydW4gaW4gZnVsbCBpbiBydW5faXQuUiwgdGFrZXMgc29tZSB0aW1lIHRvIGV4ZWN1dGUpLioqIAoKCiMjIyBHZXQgYW5kIFByZXBhcmUgRGF0YQoKKipHZXR0aW5nIGJlZXIsIHRoZSBhZ2Utb2xkIGRpbGVtbWEqKgoKKiBUaGUgQnJld2VyeURCIEFQSSByZXR1cm5zIGEgY2VydGFpbiBudW1iZXIgb2YgcmVzdWx0cyBwZXIgcGFnZTsgaWYgd2Ugd2FudCAKKiBTbywgd2UgaGl0IHRoZSBCcmV3ZXJ5REIgQVBJIGFuZCBhc2sgZm9yIGAxOm51bWJlcl9vZl9wYWdlc2AKICAgICogV2UgY2FuIGNoYW5nZSBgbnVtYmVyX29mX3BhZ2VzYCB0bywgZS5nLiwgMyBpZiB3ZSBvbmx5IHdhbnQgdGhlIGZpcnN0IDMgcGFnZXMKICAgICogSWYgdGhlcmUncyBvbmx5IG9uZSBwYWdlIChhcyBpcyB0aGUgY2FzZSBmb3IgdGhlIGdsYXNzd2FyZSBlbmRwb2luZyksIG51bWJlck9mUGFnZXMgd29uJ3QgYmUgcmV0dXJuZWQsIHNvIGluIHRoaXMgY2FzZSB3ZSBzZXQgbnVtYmVyX29mX3BhZ2VzIHRvIDEKKiBUaGUgYGFkZGl0aW9uYCBwYXJhbWV0ZXIgY2FuIGJlIGFuIGVtcHR5IHN0cmluZyBpZiBub3RoaW5nIGVsc2UgaXMgbmVlZGVkCgpgYGB7ciwgZXZhbCA9IEZBTFNFLCBlY2hvPVRSVUV9CgpiYXNlX3VybCA8LSAiaHR0cDovL2FwaS5icmV3ZXJ5ZGIuY29tL3YyIgprZXlfcHJlZmFjZSA8LSAiLz9rZXk9IgoKcGFnaW5hdGVkX3JlcXVlc3QgPC0gZnVuY3Rpb24oZXAsIGFkZGl0aW9uKSB7ICAgIAogIGZ1bGxfcmVxdWVzdCA8LSBOVUxMCiAgZmlyc3RfcGFnZSA8LSBmcm9tSlNPTihwYXN0ZTAoYmFzZV91cmwsICIvIiwgZXAsICIvIiwga2V5X3ByZWZhY2UsIGtleQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgIiZwPTEiKSkKICBudW1iZXJfb2ZfcGFnZXMgPC0gaWZlbHNlKCEoaXMubnVsbChmaXJzdF9wYWdlJG51bWJlck9mUGFnZXMpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXJzdF9wYWdlJG51bWJlck9mUGFnZXMsIDEpICAgICAgCgogICAgZm9yIChwYWdlIGluIDE6bnVtYmVyX29mX3BhZ2VzKSB7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgdGhpc19yZXF1ZXN0IDwtIGZyb21KU09OKHBhc3RlMChiYXNlX3VybCwgIi8iLCBlcCwgIi8iLCBrZXlfcHJlZmFjZSwga2V5CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgIiZwPSIsIHBhZ2UsIGFkZGl0aW9uKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmbGF0dGVuID0gVFJVRSkgCiAgICB0aGlzX3JlcV91bm5lc3RlZCA8LSB1bm5lc3RfaXQodGhpc19yZXF1ZXN0KSAgICAjICA8LSByZXF1ZXN0IHVubmVzdGVkIGhlcmUKICAgIHByaW50KHRoaXNfcmVxX3VubmVzdGVkJGN1cnJlbnRQYWdlKQogICAgZnVsbF9yZXF1ZXN0IDwtIGJpbmRfcm93cyhmdWxsX3JlcXVlc3QsIHRoaXNfcmVxX3VubmVzdGVkW1siZGF0YSJdXSkKICB9CiAgZnVsbF9yZXF1ZXN0Cn0gCgphbGxfYmVlcl9yYXcgPC0gcGFnaW5hdGVkX3JlcXVlc3QoImJlZXJzIiwgIiZ3aXRoSW5ncmVkaWVudHM9WSIpCmBgYAoKCgoqIEZ1bmN0aW9uIGZvciB1bm5lc3RpbmcgSlNPTiB1c2VkIGluc2lkZSBgcGFnaW5hdGVkX3JlcXVlc3QoKWAgYmVsb3cKICAgICsgVGFrZXMgdGhlIGNvbHVtbiBuYW1lZCBgbmFtZWAgbmVzdGVkIHdpdGhpbiBhIGNvbHVtbiBpbiB0aGUgZGF0YSBwb3J0aW9uIG9mIHRoZSByZXNwb25zZQogICAgICAgICsgSWYgdGhlIGBuYW1lYCBjb2x1bW4gZG9lc24ndCBleGlzdCwgaXQgdGFrZXMgdGhlIGZpcnN0IG5lc3RlZCBjb2x1bW4KKiBXZSB1c2Ugc29tZXRoaW5nIHNpbWlsYXIgdG8gdW5uZXN0IGluZ3JlZGllbnQgbGlrZSBhbGwgb2YgYSBiZWVyJ3MgaG9wcyBhbmQgbWFsdHMgaW50byBhIGxvbmcgc3RyaW5nIGNvbnRhaW5lZCBpbiBgaG9wc19uYW1lYCBhbmQgYG1hbHRfbmFtZWAKCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9CnVubmVzdF9pdCA8LSBmdW5jdGlvbihkZikgewogIHVubmVzdGVkIDwtIGRmCiAgZm9yKGNvbCBpbiBzZXFfYWxvbmcoZGZbWyJkYXRhIl1dKSkgewogICAgaWYoISBpcy5udWxsKG5jb2woZGZbWyJkYXRhIl1dW1tjb2xdXSkpKSB7CiAgICAgIGlmKCEgaXMubnVsbChkZltbImRhdGEiXV1bW2NvbF1dW1sibmFtZSJdXSkpIHsKICAgICAgICB1bm5lc3RlZFtbImRhdGEiXV1bW2NvbF1dIDwtIGRmW1siZGF0YSJdXVtbY29sXV1bWyJuYW1lIl1dCiAgICAgIH0gZWxzZSB7CiAgICAgICAgdW5uZXN0ZWRbWyJkYXRhIl1dW1tjb2xdXSA8LSBkZltbImRhdGEiXV1bW2NvbF1dW1sxXV0KICAgICAgfQogICAgfQogIH0KICB1bm5lc3RlZAp9CmBgYAoKCgoqKkNvbGxhcHNlIFN0eWxlcyoqCgoqIFNhdmUgdGhlIG1vc3QgcG9wdWxhciBzdHlsZXMgaW4gYGtleXdvcmRzYAoqIExvb3AgdGhyb3VnaCBlYWNoIGtleXdvcmQKICAgICogRm9yIGVhY2ggYmVlciwgYGdyZXBgIHRocm91Z2ggaXRzIHN0eWxlIGNvbHVtbiB0byBzZWUgaWYgaXQgY29udGFpbnMgYW55IG9uZSBvZiB0aGVzZSBrZXl3b3JkcwogICAgKiBJZiBpdCBkb2VzLCBnaXZlIGl0IHRoYXQga2V5d29yZCBpbiBhIG5ldyBjb2x1bW4gYHN0eWxlX2NvbGxhcHNlZGAKKiBJZiBhIGJlZXIncyBuYW1lIG1hdGNoZXMgbXVsdGlwbGUga2V5d29yZHMsIGUuZy4sIEFtZXJpY2FuIERvdWJsZSBJbmRpYSBQYWxlIEFsZSB3b3VsZCBtYXRjaCBEb3VibGUgSW5kaWEgUGFsZSBBbGUsIEluZGlhIFBhbGUgQWxlLCBhbmQgUGFsZSBBbGUsIGl0cyBgc3R5bGVfY29sbGFwc2VkYCBpcyB0aGUgKipsYXN0Kiogb2YgdGhvc2UgdGhhdCBhcHBlYXIgaW4ga2V5d29yZHMgCiAgICAqIFRoaXMgaXMgd2h5IGtleXdvcmRzIGFyZSBpbnRlbnRpb25hbGx5IG9yZGVyZWQgZnJvbSBtb3N0IGdlbmVyYWwgdG8gbW9zdCBzcGVjaWZpYwogICAgKiBTbyBpbiB0aGUgY2FzZSBvZiBhbiBjYXNlIG9mIEFtZXJpY2FuIERvdWJsZSBJbmRpYSBQYWxlIEFsZTogc2luY2UgRG91YmxlIEluZGlhIFBhbGUgQWxlIGFwcGVhcnMgaW4gYGtleXdvcmRzYCBhZnRlciBJbmRpYSBQYWxlIEFsZSBhbmQgUGFsZSBBbGUsIGFuIEFtZXJpY2FuIERvdWJsZSBJbmRpYSBQYWxlIEFsZSB3b3VsZCBnZXQgYSBgc3R5bGVfY29sbGFwc2VkYCBvZiBEb3VibGUgSW5kaWEgUGFsZSBBbGUKKiBJZiBubyBrZXl3b3JkIGlzIGNvbnRhaW5lZCBpbiBgc3R5bGVgLCBgc3R5bGVfY29sbGFwc2VkYCBpcyBqdXN0IHdoYXRldmVyJ3MgaW4gYHN0eWxlYDsgaW4gb3RoZXIgd29yZHMsIGl0IGRvZXNuJ3QgZ2V0IGNvbGxwc2VkIGludG8gYSBiaWdnZXIgYnVja2V0CiAgICAqIFRoaXMgaXNuJ3QgYSBodWdlIHByb2JsZW0gYmVjYXVzZSB3ZSdsbCBwYXJlIGRvd24gdG8ganVzdCB0aGUgbW9zdCBwb3B1bGFyIHN0eWxlcyBsYXRlciwgaG93ZXZlciB3ZSBjb3VsZCB0aGluayBhYm91dCBjcmVhdGluZyBhIGNhdGNoYWxsICJPdGhlciIgbGV2ZWwgZm9yIGBzdHlsZV9jb2xsYXBzZWRgCgpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQoKY29sbGFwc2Vfc3R5bGVzIDwtIGZ1bmN0aW9uKGRmKSB7CiAga2V5d29yZHMgPC0gYygiTGFnZXIiLCAiUGFsZSBBbGUiLCAiSW5kaWEgUGFsZSBBbGUiLCAiRG91YmxlIEluZGlhIFBhbGUgQWxlIiwgIkluZGlhIFBhbGUgTGFnZXIiLCAiSGVmZXdlaXplbiIsICJCYXJyZWwtQWdlZCIsIldoZWF0IiwgIlBpbHNuZXIiLCAiUGlsc2VuZXIiLCAiQW1iZXIiLCAiR29sZGVuIiwgIkJsb25kZSIsICJCcm93biIsICJCbGFjayIsICJTdG91dCIsICJQb3J0ZXIiLCAiUmVkIiwgIlNvdXIiLCAiS8O2bHNjaCIsICJUcmlwZWwiLCAiQml0dGVyIiwgIlNhaXNvbiIsICJTdHJvbmcgQWxlIiwgIkJhcmxleSBXaW5lIiwgIkR1YmJlbCIsICJBbHRiaWVyIikKICAKICBmb3IgKGJlZXIgaW4gMTpucm93KGRmKSkgewogICAgaWYgKGdyZXBsKHBhc3RlKGtleXdvcmRzLCBjb2xsYXBzZT0ifCIpLCBkZiRzdHlsZVtiZWVyXSkpIHsgICAgCiAgICAgIGZvciAoa2V5d29yZCBpbiBrZXl3b3JkcykgeyAgICAgICAgIAogICAgICAgIGlmKGdyZXBsKGtleXdvcmQsIGRmJHN0eWxlW2JlZXJdKSA9PSBUUlVFKSB7CiAgICAgICAgICBkZiRzdHlsZV9jb2xsYXBzZWRbYmVlcl0gPC0ga2V5d29yZCAgICAKICAgICAgICB9ICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICB9IAogICAgfSBlbHNlIHsKICAgICAgZGYkc3R5bGVfY29sbGFwc2VkW2JlZXJdIDwtIGFzLmNoYXJhY3RlcihkZiRzdHlsZVtiZWVyXSkgICAgICAgCiAgICB9CiAgICBwcmludChkZiRzdHlsZV9jb2xsYXBzZWRbYmVlcl0pCiAgfQogIHJldHVybihkZikKfQoKYGBgCgoqIFRoZW4gd2UgY29sbGFwc2UgZnVydGhlcjsgcmlnaHQgbm93IHdlIGp1c3QgY29tYmluZSBhbGwgd2hlYXR5IGJlYXJzIGludG8gV2hlYXQgYW5kIFBpbHMtbGlrZSBiZWVycyBpbnRvIFBpbHNlbmVyICh3aXRoIHR3byBlJ3MpIGJ5IGBmY3RfY29sbGFwc2VgaW5nIHRob3NlIGxldmVscwoKYGBge3IsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0KY29sbGFwc2VfZnVydGhlciA8LSBmdW5jdGlvbihkZikgewogIGRmW1sic3R5bGVfY29sbGFwc2VkIl1dIDwtIGRmW1sic3R5bGVfY29sbGFwc2VkIl1dICU+JQogICAgZmN0X2NvbGxhcHNlKAogICAgICAiV2hlYXQiID0gYygiSGVmZXdlaXplbiIsICJXaGVhdCIpLAogICAgICAiUGlsc2VuZXIiID0gYygiUGlsc25lciIsICJBbWVyaWNhbi1TdHlsZSBQaWxzZW5lciIpICMgcGlsc2VuZXIgPT0gcGlsc25lciA9PSBwaWxzCiAgICApCiAgcmV0dXJuKGRmKQp9CmBgYAoKCgoqKlNwbGl0IG91dCBJbmdyZWRpZW50cyoqCgoqIFdoZW4gd2UgdW5uZXN0ZWQgaW5ncmVkaWVudHMsIHdlIGp1c3QgY29uY2F0ZW5hdGVkIGFsbCBvZiB0aGUgaW5ncmVkaWVudHMgZm9yIGEgZ2l2ZW4gYmVlciBpbnRvIGEgbG9uZyBzdHJpbmcKKiBJZiB3ZSB3YW50LCB3ZSBjYW4gc3BsaXQgb3V0IHRoZSBpbmdyZWRpZW50cyB0aGF0IHdlcmUgY29uY2F0ZW5hdGVkIGluIGA8aW5ncmVkaWVudD5fbmFtZWAgd2l0aCB0aGlzIGBzcGxpdF9pbmdyZWRpZW50c2AgZnVuY3Rpb24KKiBUaGlzIHRha2VzIGEgdmVjdG9yIG9mIGBpbmdyZWRpZW50c190b19zcGxpdGAsIHNvIGUuZy4gYGMoImhvcHNfbmFtZSIsICJtYWx0X25hbWUiKWAgYW5kIGNyZWF0ZXMgb25lIGNvbHVtbiBmb3IgZWFjaCB0eXBlIG9mIGluZ3JlZGllbnQgKGBob3BzX25hbWVfMWAsIGBob3BzX25hbWVfMmAsIGV0Yy4pCgoqIFdlIGBzdHJfc3BsaXRgIG9uIHRoZSBpbmdyZWRpZW50IGFuZCBnZXQgYSBsaXN0IGJhY2sKKiBXZSBmaW5kIHRoZSBtYXggbnVtYmVyIG9mIGluc3RhbmNlcyBvZiBhbiBpbmdyZWRpZW50IHBlciBiZWVyLCB3aGljaCB3aWxsIGJlIHRoZSBudW1iZXIgb2YgY29sdW1ucyB3ZSdyZSBhZGRpbmcKKiBGb3IgZWFjaCBuZXcgY29sdW1uIHdlIG5lZWQsIHdlIGNyZWF0ZSBpdCwgaW5pdGlhbGl6ZSBpdCB3aXRoIE5BcywgYW5kIG5hbWUgaXQKKiBUaGVuIGZvciBlYWNoIGVsZW1lbnQgaW4gb3VyIGxpc3Qgb2Ygc3BsaXQgdXAgaW5ncmVkaWVudHMsIGlmIGl0IGV4aXN0cywgd2UgYWRkIGl0IHRvIHRoZSBjb3JyZWN0IGNvbHVtbiBpbiBvdXIgZGYKCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9CnNwbGl0X2luZ3JlZGllbnRzIDwtIGZ1bmN0aW9uKGRmLCBpbmdyZWRpZW50c190b19zcGxpdCkgewogIAogIG5jb2xfZGYgPC0gbmNvbChkZikKICAKICBmb3IgKGluZ3JlZGllbnQgaW4gaW5ncmVkaWVudHNfdG9fc3BsaXQpIHsKCiAgICBpbmdyZWRpZW50X3NwbGl0IDwtIHN0cl9zcGxpdChkZltbaW5ncmVkaWVudF1dLCAiLCAiKSAgICAKICAgIG51bV9uZXdfY29scyA8LSBtYXgobGVuZ3RocyhpbmdyZWRpZW50X3NwbGl0KSkgICAgCiAgCiAgICBmb3IgKG51bSBpbiAxOm51bV9uZXdfY29scykgewogICAgICAKICAgICAgdGhpc19jb2wgPC0gbmNvbF9kZiArIDEgICAgICAgICAKICAgICAgCiAgICAgIGRmWywgdGhpc19jb2xdIDwtIE5BCiAgICAgIG5hbWVzKGRmKVt0aGlzX2NvbF0gPC0gcGFzdGUwKGluZ3JlZGllbnQsICJfIiwgbnVtKQogICAgICBuY29sX2RmIDwtIG5jb2woZGYpICAgICAgICAgICAgIAogICAgICBmb3IgKHJvdyBpbiBzZXFfYWxvbmcoaW5ncmVkaWVudF9zcGxpdCkpIHsgICAgICAgICAgCiAgICAgICAgaWYgKCFpcy5udWxsKGluZ3JlZGllbnRfc3BsaXRbW3Jvd11dW251bV0pKSB7ICAgICAgICAKICAgICAgICAgIGRmW3JvdywgdGhpc19jb2xdIDwtIGluZ3JlZGllbnRfc3BsaXRbW3Jvd11dW251bV0KICAgICAgICB9CiAgICAgIH0KICAgICAgZGZbW25hbWVzKGRmKVt0aGlzX2NvbF1dXSA8LSBmYWN0b3IoZGZbW25hbWVzKGRmKVt0aGlzX2NvbF1dXSkKICAgIH0KICAgIAogICAgbmNvbF9kZiA8LSBuY29sKGRmKQogIH0KICByZXR1cm4oZGYpCn0KYGBgCgoKCioqRmluZCB0aGUgTW9zdCBQb3B1YWxhciBTdHlsZXMqKgoKKiBGaW5kIG1lYW4gQUJWLCBJQlUsIGFuZCBTUk0gcGVyIGNvbGxhcHNlZCBzdHlsZQoqIEFycmFuZ2UgY29sbGFwc2VkIHN0eWxlcyBieSB0aGUgbnVtYmVyIG9mIGJlZXJzIHRoYXQgZmFsbCBpbnRvIHRoZW0KICAgICogVGhpcyBpcyBvZiBjb3Vyc2UgZGVwZW5kZW50IG9uIGhvdyB3ZSBjb2xsYXBzZSBzdHlsZXMKCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9CmxpYnJhcnkoZm9yY2F0cykKCiMgUGFyZSBkb3duIHRvIG9ubHkgY2FzZXMgd2hlcmUgc3R5bGUgaXMgbm90IE5BCmJlZXJfZGF0IDwtIGJlZXJfbmVjZXNzaXRpZXMKCmJlZXJfZGF0X3BhcmVkIDwtIGJlZXJfZGF0W2NvbXBsZXRlLmNhc2VzKGJlZXJfZGF0JHN0eWxlKSwgXQoKIyBBcnJhbmdlIGJlZXIgZGF0IGJ5IHN0eWxlIHBvcHVsYXJpdHkKc3R5bGVfcG9wdWxhcml0eSA8LSBiZWVyX2RhdF9wYXJlZCAlPiUgCiAgZ3JvdXBfYnkoc3R5bGUpICU+JSAKICBjb3VudCgpICU+JSAKICBhcnJhbmdlKGRlc2MobikpCnN0eWxlX3BvcHVsYXJpdHkKCiMgQWRkIGEgY29sdW1uIHRoYXQgc2NhbGVzIHBvcHVsYXJpdHkKc3R5bGVfcG9wdWxhcml0eSA8LSBiaW5kX2NvbHMoc3R5bGVfcG9wdWxhcml0eSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX3NjYWxlZCA9IGFzLnZlY3RvcihzY2FsZShzdHlsZV9wb3B1bGFyaXR5JG4pKSkKCiMgRmluZCBzdHlsZXMgdGhhdCBhcmUgYWJvdmUgYSB6LXNjb3JlIG9mIDAKcG9wdWxhcl9zdHlsZXMgPC0gc3R5bGVfcG9wdWxhcml0eSAlPiUgCiAgZmlsdGVyKG5fc2NhbGVkID4gMCkKCiMgUGFyZSBkYXQgZG93biB0byBvbmx5IGJlZXJzIHRoYXQgZmFsbCBpbnRvIHRob3NlIHN0eWxlcwpwb3B1bGFyX2JlZXJfZGF0IDwtIGJlZXJfZGF0X3BhcmVkICU+JSAKICBmaWx0ZXIoCiAgICBzdHlsZSAlaW4lIHBvcHVsYXJfc3R5bGVzJHN0eWxlCiAgKSAlPiUgCiAgZHJvcGxldmVscygpICU+JSAKICBhc190aWJibGUoKSAKbnJvdyhwb3B1bGFyX2JlZXJfZGF0KQoKIyBGaW5kIHRoZSBjZW50ZXJzIChtZWFuIGFidiwgaWJ1LCBzcm0pIG9mIHRoZSBtb3N0IHBvcHVsYXIgc3R5bGVzCnN0eWxlX2NlbnRlcnMgPC0gcG9wdWxhcl9iZWVyX2RhdCAlPiUgCiAgZ3JvdXBfYnkoc3R5bGVfY29sbGFwc2VkKSAlPiUgCiAgYWRkX2NvdW50KCkgJT4lIAogIHN1bW1hcmlzZSgKICAgIG1lYW5fYWJ2ID0gbWVhbihhYnYsIG5hLnJtID0gVFJVRSksCiAgICBtZWFuX2lidSA9IG1lYW4oaWJ1LCBuYS5ybSA9IFRSVUUpLCAKICAgIG1lYW5fc3JtID0gbWVhbihzcm0sIG5hLnJtID0gVFJVRSksCiAgICBuID0gbWVkaWFuKG4sIG5hLnJtID0gVFJVRSkgICAgICAgICAgIyBNZWRpYW4gaGVyZSBvbmx5IGZvciBzdW1tYXJpc2UuIFNob3VsZCBiZSBqdXN0IHRoZSBzYW1lIGFzIG4KICApICU+JSAKICBhcnJhbmdlKGRlc2MobikpICU+JSAKICBkcm9wX25hKCkgJT4lIAogIGRyb3BsZXZlbHMoKQoKIyBHaXZlIHNvbWUgbmljZXIgbmFtZXMKc3R5bGVfY2VudGVyc19yZW5hbWUgPC0gc3R5bGVfY2VudGVycyAlPiUgCiAgcmVuYW1lKAogICAgYENvbGxhcHNlZCBTdHlsZWAgPSBzdHlsZV9jb2xsYXBzZWQsCiAgICBgTWVhbiBBQlZgID0gbWVhbl9hYnYsCiAgICBgTWVhbiBJQlVgID0gbWVhbl9pYnUsCiAgICBgTWVhbiBTUk1gID0gbWVhbl9zcm0sCiAgICBgTnVtZXIgb2YgQmVlcnNgID0gbgogICkKYGBgCgoKVGFrZSBhIGxvb2sgYXQgdGhlIHRhYmxlICAgICAgCgpgYGB7cn0Ka2FibGUoc3R5bGVfY2VudGVyc19yZW5hbWUpCmBgYAoKCioqKgoKTm93IHRoYXQgdGhlIG11bmdpbmcgaXMgZG9uZSwgb250byB0aGUgbWFpbiBxdWVzdGlvbjogZG8gbmF0dXJhbCBjbHVzdGVycyBpbiBiZWVyIGFsaWduIHdpdGggc3R5bGUgYm91bmRhcmllcz8KCgoKCioqKgoKIyMjIEluZ3JlZGllbnRzCgpUbyBnZXQgbW9yZSBncmFudWxhciB3aXRoIGluZ3JlZGllbnRzLCB3ZSBjYW4gc3BsaXQgb3V0IGVhY2ggaW5kaXZpZHVhbCBpbmdyZWRpZW50IGludG8gaXRzIG93biBjb2x1bW4uIElmIGEgYmVlciBvciBzdHlsZSBjb250YWlucyB0aGF0IGluZ3JlZGllbnQsIGl0cyByb3cgZ2V0cyBhIDEgaW4gdGhhdCBpbmdyZWRpZW50IGNvbHVtbiBhbmQgYSAwIG90aGVyd2lzZS4KCkZyb20gdGhpcywgd2UgY2FuIGZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiBob3BzIGFuZCBtYWx0cyBwZXIgZ3JvdXBlci4KCiogVGhlIGRhdGFmcmFtZSB3ZSdsbCB1c2Ugd2lsbCBiZSBgYmVlcl9uZWNlc3NpdGllc2AKCgo8IS0tIGBgYHtyLCBldmFsPVRSVUUsIGVjaG89VFJVRX0gLS0+CjwhLS0gY2x1c3RlcmVkX2JlZXJfbmVjZXNzaXRpZXMgPC0gY2x1c3RlcmVkX2JlZXIgJT4lIC0tPgo8IS0tICAgaW5uZXJfam9pbihiZWVyX25lY2Vzc2l0aWVzKSAtLT4KCjwhLS0gYGBgIC0tPgoKKiBUaGlzIGZ1bmN0aW9uIHRha2VzIGEgZGF0YWZyYW1lIGFuZCB0d28gb3RoZXIgcGFyYW1ldGVycyBzZXQgYXQgdGhlIG91dHNldDoKICAgICogYGluZ3JlZGllbnRfd2FudGA6IHRoaXMgY2FuIGJlIGBob3BzYCwgYG1hbHRgLCBvciBvdGhlciBpbmdyZWRpZW50cyBsaWtlIGB5ZWFzdGAgaWYgd2UgcHVsbCB0aGF0IGluCiAgICAqIGBncm91cGVyYDogY2FuIGJlIGEgdmVjdG9yIG9mIG9uZSBvciBtb3JlIHRoaW5ncyB0byBncm91cCBieSwgbGlrZSBiZWVyIGBuYW1lYCBvciBgc3R5bGVgCgpgYGB7ciwgZXZhbD1UUlVFLCBlY2hvPVRSVUV9CgpwaWNrX2luZ3JlZGllbnRfZ2V0X2JlZXIgPC0gZnVuY3Rpb24gKGluZ3JlZGllbnRfd2FudCwgZGYsIGdyb3VwZXIpIHsKICAKICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIFNldHVwIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAjCiAgIyBXZSd2ZSBhbHJlYWR5IHNwbGl0IGluZ3JlZGllbnQgbnVtYmVyIG5hbWVzIG91dCBmcm9tIHRoZSBjb25jYXRlbmF0ZWQgc3RyaW5nIGludG8gY29sdW1ucyBsaWtlIGBtYWx0X25hbWVfMWAsCiAgIyBgbWFsdF9uYW1lXzJgLCBldGMuIFdlIG5lZWQgdG8gZmluZCB0aGUgcmFuZ2Ugb2YgdGhlc2UgY29sdW1uczsgdGhlcmUgd2lsbCBiZSBhIGRpZmZlcmVudCBudW1iZXIgb2YgbWFsdAogICMgY29sdW1ucyB0aGFuIGhvcHMgY29sdW1ucywgZm9yIGluc3RhbmNlLiBUaGUgZmlyc3Qgb25lIHdpbGwgYmUgYDxpbmdyZWRpZW50Pl9uYW1lXzFgIGFuZCBmcm9tIHRoaXMgd2UgY2FuIGZpbmQKICAjIHRoZSBpbmRleCBvZiB0aGlzIGNvbHVtbiBpbiBvdXIgZGF0YWZyYW1lLiBXZSBnZXQgdGhlIG5hbWUgb2YgbGFzdCBvbmUgd2l0aCB0aGUgYGdldF9sYXN0X2luZ19uYW1lX2NvbCgpYAogICMgZnVuY3Rpb24uIFRoZW4gd2Ugc2F2ZSBhIHZlY3RvciBvZiBhbGwgdGhlIGluZ3JlZGllbnQgY29sdW1uIG5hbWVzIGluIGBpbmdyZWRpZW50X2NvbG5hbWVzYC4gSXQgd2lsbCBzdGF5CiAgIyBjb25zdGFudCBldmVuIGlmIHRoZSBpbmRpY2VzIGNoYW5nZSB3aGVuIHdlIHNlbGVjdCBvdXQgY2VydGFpbiBjb2x1bW5zLiAKICAKICAjIEZpcnN0IGluZ3JlZGllbnQKICBmaXJzdF9pbmdyZWRpZW50X25hbWUgPC0gcGFzdGUoaW5ncmVkaWVudF93YW50LCAiX25hbWVfMSIsIHNlcD0iIikKICBmaXJzdF9pbmdyZWRpZW50X2luZGV4IDwtIHdoaWNoKGNvbG5hbWVzKGRmKT09Zmlyc3RfaW5ncmVkaWVudF9uYW1lKQogIAogICMgR2V0IHRoZSBsYXN0IGluZ3JlZGllbnQKICBnZXRfbGFzdF9pbmdfbmFtZV9jb2wgPC0gZnVuY3Rpb24oZGYpIHsKICAgIGZvciAoY29sIGluIG5hbWVzKGRmKSkgewogICAgICBpZiAoZ3JlcGwocGFzdGUoaW5ncmVkaWVudF93YW50LCAiX25hbWVfIiwgc2VwID0gIiIpLCBjb2wpID09IFRSVUUpIHsKICAgICAgICBuYW1lX2xhc3RfaW5nX2NvbCA8LSBjb2wKICAgICAgfQogICAgfQogICAgcmV0dXJuKG5hbWVfbGFzdF9pbmdfY29sKQogIH0KICAKICAjIExhc3QgaW5ncmVkaWVudAogIGxhc3RfaW5ncmVkaWVudF9uYW1lIDwtIGdldF9sYXN0X2luZ19uYW1lX2NvbChkZikKICBsYXN0X2luZ3JlZGllbnRfaW5kZXggPC0gd2hpY2goY29sbmFtZXMoZGYpPT1sYXN0X2luZ3JlZGllbnRfbmFtZSkKICAKICAjIFZlY3RvciBvZiBhbGwgdGhlIGluZ3JlZGllbnQgY29sdW1uIG5hbWVzCiAgaW5ncmVkaWVudF9jb2xuYW1lcyA8LSBuYW1lcyhkZilbZmlyc3RfaW5ncmVkaWVudF9pbmRleDpsYXN0X2luZ3JlZGllbnRfaW5kZXhdCiAgCiAgIyBOb24taW5ncmVkaWVudCBjb2x1bW4gbmFtZXMgd2Ugd2FudCB0byBrZWVwCiAgdG9fa2VlcF9jb2xfbmFtZXMgPC0gYygiY2x1c3Rlcl9hc3NpZ25tZW50IiwgIm5hbWUiLCAiYWJ2IiwgImlidSIsICJzcm0iLCAic3R5bGUiLCAic3R5bGVfY29sbGFwc2VkIikKICAKICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0jIAogIAogICMgSW5zaWRlIGBnYXRoZXJfaW5ncmVkaWVudHMoKWAgd2UgdGFrZSBvdXQgc3VwZXJmbG91cyBjb2x1bW4gbmFtZXMgdGhhdCBhcmUgbm90IGluIGB0b19rZWVwX2NvbF9uYW1lc2Agb3Igb25lIAogICMgb2YgdGhlIGluZ3JlZGllbnQgY29sdW1ucywgZmluZCB3aGF0IHRoZSBuZXcgaW5ncmVkaWVudCBjb2x1bW4gaW5kaWNlcyBhcmUsIHNpbmNlIHRoZXknbGwgaGF2ZSBjaGFuZ2VkIGFmdGVyIAogICMgd2UgcGFyZWQgZG93biBhbmQgdGhlbiBnYXRoZXIgYWxsIG9mIHRoZSBpbmdyZWRpZW50IGNvbHVtbnMgKGUuZy4sIGBob3BzX25hbWVfMWApIGludG8gb25lIGxvbmcgY29sdW1uLCAKICAjIGBpbmdfa2V5c2AgYW5kIGFsbCB0aGUgYWN0dWFsIGluZ3JlZGllbnQgbmFtZXMgKGUuZy4sIENhc2NhZGUpIGludG8gYGluZ19uYW1lc2AuCiAgCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBHYXRoZXIgY29sdW1ucyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gIwogIGdhdGhlcl9pbmdyZWRpZW50cyA8LSBmdW5jdGlvbihkZiwgY29sc190b19nYXRoZXIpIHsKICAgIHRvX2tlZXBfaW5kaWNlcyA8LSB3aGljaChjb2xuYW1lcyhkZikgJWluJSB0b19rZWVwX2NvbF9uYW1lcykKICAgIAogICAgc2VsZWN0ZWRfZGYgPC0gZGZbLCBjKHRvX2tlZXBfaW5kaWNlcywgZmlyc3RfaW5ncmVkaWVudF9pbmRleDpsYXN0X2luZ3JlZGllbnRfaW5kZXgpXQogICAgCiAgICBuZXdfaW5nX2luZGljZXMgPC0gd2hpY2goY29sbmFtZXMoc2VsZWN0ZWRfZGYpICVpbiUgY29sc190b19nYXRoZXIpICAgICMgaW5kaWNlcyB3aWxsIGhhdmUgY2hhbmdlZCBzaW5jZSB3ZSBwYXJlZCBkb3duIAogICAgCiAgICBkZl9nYXRoZXJlZCA8LSBzZWxlY3RlZF9kZiAlPiUKICAgICAgZ2F0aGVyXygKICAgICAgICBrZXlfY29sID0gImluZ19rZXlzIiwKICAgICAgICB2YWx1ZV9jb2wgPSAiaW5nX25hbWVzIiwKICAgICAgICBnYXRoZXJfY29scyA9IGNvbG5hbWVzKHNlbGVjdGVkX2RmKVtuZXdfaW5nX2luZGljZXNdCiAgICAgICkgJT4lCiAgICAgIG11dGF0ZSgKICAgICAgICBjb3VudCA9IDEKICAgICAgKQogICAgZGZfZ2F0aGVyZWQKICB9CiAgYmVlcl9nYXRoZXJlZCA8LSBnYXRoZXJfaW5ncmVkaWVudHMoZGYsIGluZ3JlZGllbnRfY29sbmFtZXMpICAjIGluZ3JlZGllbnQgY29sbmFtZXMgZGVmaW5lZCBhYm92ZSBmdW5jdGlvbgogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAjIAogIAogICMgTmV4dCB3ZSBnZXQgYSB2ZWN0b3Igb2YgYWxsIGluZ3JlZGllbnQgbGV2ZWxzIGFuZCB0YWtlIG91dCB0aGUgb25lIHRoYXQncyBhbiBlbXB0eSBzdHJpbmcgYW5kIAogICMgdXNlIHRoaXMgdmVjdG9yIG9mIGluZ3JlZGllbnQgbGV2ZWxzIGluIGBzZWxlY3Rfc3ByZWFkX2NvbHMoKWAgYmVsb3cKCiAgIyBHZXQgYSB2ZWN0b3Igb2YgYWxsIGluZ3JlZGllbnQgbGV2ZWxzCiAgYmVlcl9nYXRoZXJlZCRpbmdfbmFtZXMgPC0gZmFjdG9yKGJlZXJfZ2F0aGVyZWQkaW5nX25hbWVzKQogIGluZ3JlZGllbnRfbGV2ZWxzIDwtIGxldmVscyhiZWVyX2dhdGhlcmVkJGluZ19uYW1lcykgCiAgCiAgIyBUYWtlIG91dCB0aGUgbGV2ZWwgdGhhdCdzIGp1c3QgYW4gZW1wdHkgc3RyaW5nCiAgdG9fa2VlcF9sZXZlbHMgPC0gIShjKDE6bGVuZ3RoKGluZ3JlZGllbnRfbGV2ZWxzKSkgJWluJSB3aGljaChpbmdyZWRpZW50X2xldmVscyA9PSAiIikpCiAgaW5ncmVkaWVudF9sZXZlbHMgPC0gaW5ncmVkaWVudF9sZXZlbHNbdG9fa2VlcF9sZXZlbHNdCiAgCiAgYmVlcl9nYXRoZXJlZCRpbmdfbmFtZXMgPC0gYXMuY2hhcmFjdGVyKGJlZXJfZ2F0aGVyZWQkaW5nX25hbWVzKQogIAogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gIyAKICAKICAjIFRoZW4gd2Ugc3ByZWFkIHRoZSBpbmdyZWRpZW50IG5hbWVzOiB3ZSB0YWtlIHdoYXQgd2FzIHByZXZpb3VzbHkgdGhlIGB2YWx1ZWAgaW4gb3VyIGdhdGhlcmVkIGRhdGFmcmFtZSwgdGhlCiAgIyBhY3R1YWwgaW5ncmVkaWVudCBuYW1lcyAoQ2FzY2FkZSwgQ2VudGVubmlhbCkgYW5kIG1ha2UgdGhhdCBvdXIgYGtleWA7IGl0J2xsIGZvcm0gdGhlIG5ldyBjb2x1bW4gbmFtZXMuIFRoZQogICMgbmV3IGB2YWx1ZWAgaXMgYHZhbHVlYCBpcyBjb3VudDsgaXQnbGwgcG9wdWxhdGUgdGhlIHJvdyBjZWxscy4gSWYgYSBnaXZlbiByb3cgaGFzIGEgY2VydGFpbiBpbmdyZWRpZW50LCBpdAogICMgZ2V0cyBhIDEgaW4gdGhlIGNvcnJlc3BvbmRpbmcgY2VsbCwgYW4gTkEgb3RoZXJ3aXNlLiAKICAjIFdlIGFkZCBhIHVuaXF1ZSBpZGVuZml0aWVyIGZvciBlYWNoIHJvdyB3aXRoIGByb3dgLCB3aGljaCB3ZSdsbCBkcm9wIGxhdGVyIChzZWUgW0hhZGxleSdzIFNPCiAgIyBjb21tZW50XShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy8yNTk2MDM5NC91bmV4cGVjdGVkLWJlaGF2aW9yLXdpdGgtdGlkeXIpKS4KCiAgCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIFNwcmVhZCBjb2x1bW5zIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICMKICBzcHJlYWRfaW5ncmVkaWVudHMgPC0gZnVuY3Rpb24oZGYpIHsKICAgIGRmX3NwcmVhZCA8LSBkZiAlPiUgCiAgICAgIG11dGF0ZSgKICAgICAgICByb3cgPSAxOm5yb3coZGYpICAgICAgICAjIEFkZCBhIHVuaXF1ZSBpZGVuZml0aWVyIGZvciBlYWNoIHJvdyB3aGljaCB3ZSdsbCBuZWVkIGluIG9yZGVyIHRvIHNwcmVhZDsgd2UnbGwgZHJvcCB0aGlzIGxhdGVyCiAgICAgICkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgIHNwcmVhZCgKICAgICAgICBrZXkgPSBpbmdfbmFtZXMsCiAgICAgICAgdmFsdWUgPSBjb3VudAogICAgICApIAogICAgcmV0dXJuKGRmX3NwcmVhZCkKICB9CiAgYmVlcl9zcHJlYWQgPC0gc3ByZWFkX2luZ3JlZGllbnRzKGJlZXJfZ2F0aGVyZWQpCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICMgCgogIAogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSBTZWxlY3Qgb25seSBjZXJ0YWluIGNvbHVtbnMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAjCiAgc2VsZWN0X3NwcmVhZF9jb2xzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgICB0b19rZWVwX2NvbF9pbmRpY2VzIDwtIHdoaWNoKGNvbG5hbWVzKGRmKSAlaW4lIHRvX2tlZXBfY29sX25hbWVzKQogICAgdG9fa2VlcF9pbmdyZWRpZW50X2luZGljZXMgPC0gd2hpY2goY29sbmFtZXMoZGYpICVpbiUgaW5ncmVkaWVudF9sZXZlbHMpCiAgICAKICAgIHRvX2tlZXBfaW5kc19hbGwgPC0gYyh0b19rZWVwX2NvbF9pbmRpY2VzLCB0b19rZWVwX2luZ3JlZGllbnRfaW5kaWNlcykKICAgIAogICAgbmV3X2RmIDwtIGRmICU+JSAKICAgICAgc2VsZWN0XygKICAgICAgICAuZG90cyA9IHRvX2tlZXBfaW5kc19hbGwKICAgICAgKQogICAgcmV0dXJuKG5ld19kZikKICB9CiAgYmVlcl9zcHJlYWRfc2VsZWN0ZWQgPC0gc2VsZWN0X3NwcmVhZF9jb2xzKGJlZXJfc3ByZWFkKQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAjIAoKICAjIFRha2Ugb3V0IGFsbCByb3dzIHRoYXQgaGF2ZSBubyBpbmdyZWRpZW50cyBzcGVjaWZpZWQgYXQgYWxsCiAgaW5kc190b19yZW1vdmUgPC0gYXBwbHkoYmVlcl9zcHJlYWRfc2VsZWN0ZWRbLCBmaXJzdF9pbmdyZWRpZW50X2luZGV4Omxhc3RfaW5ncmVkaWVudF9pbmRleF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgIDEsIGZ1bmN0aW9uKHgpIGFsbChpcy5uYSh4KSkpCiAgYmVlcl9zcHJlYWRfbm9fbmEgPC0gYmVlcl9zcHJlYWRfc2VsZWN0ZWRbICFpbmRzX3RvX3JlbW92ZSwgXQogIAogIAogICMgLS0tLS0tLS0tLS0tLS0tLS0gR3JvdXAgaW5ncmVkaWVudHMgYnkgdGhlIGdyb3VwZXIgc3BlY2lmaWVkIC0tLS0tLS0tLS0tLS0tLS0tLS0gIwogICMgVGhlbiB3ZSBkbyB0aGUgZmluYWwgc3RlcCBhbmQgZ3JvdXAgYnkgdGhlIGdyb3VwZXJzLgogIAogIGdldF9pbmdyZWRpZW50c19wZXJfZ3JvdXBlciA8LSBmdW5jdGlvbihkZiwgZ3JvdXBlciA9IGdyb3VwZXIpIHsKICAgIGRmX2dyb3VwZWQgPC0gZGYgJT4lCiAgICAgIHVuZ3JvdXAoKSAlPiUgCiAgICAgIGdyb3VwX2J5Xyhncm91cGVyKQogICAgCiAgICBub3RfZm9yX3N1bW1pbmcgPC0gd2hpY2goY29sbmFtZXMoZGZfZ3JvdXBlZCkgJWluJSB0b19rZWVwX2NvbF9uYW1lcykKICAgIG1heF9ub3RfZm9yX3N1bW1pbmcgPC0gbWF4KG5vdF9mb3Jfc3VtbWluZykKICAgIAogICAgcGVyX2dyb3VwZXIgPC0gZGZfZ3JvdXBlZCAlPiUgCiAgICAgIHNlbGVjdCgtYyhhYnYsIGlidSwgc3JtKSkgJT4lICAgICMgdGFraW5nIG91dCB0ZW1wb3JhcmlseQogICAgICBzdW1tYXJpc2VfaWYoCiAgICAgICAgaXMubnVtZXJpYywgICAgICAgICAgICAgIAogICAgICAgIHN1bSwgbmEucm0gPSBUUlVFCiAgICAgICAgIyAtYyhhYnYsIGlidSwgc3JtKQogICAgICApICU+JQogICAgICBtdXRhdGUoCiAgICAgICAgdG90YWwgPSByb3dTdW1zKC5bKG1heF9ub3RfZm9yX3N1bW1pbmcgKyAxKTpuY29sKC4pXSwgbmEucm0gPSBUUlVFKSAgICAKICAgICAgKQogICAgCiAgICAjIFNlbmQgdG90YWwgdG8gdGhlIHNlY29uZCBwb3NpdGlvbgogICAgcGVyX2dyb3VwZXIgPC0gcGVyX2dyb3VwZXIgJT4lIAogICAgICBzZWxlY3QoCiAgICAgICAgbmFtZSwgdG90YWwsIGV2ZXJ5dGhpbmcoKQogICAgICApCiAgICAKICAgICMgUmVwbGFjZSB0b3RhbCBjb2x1bW4gd2l0aCBtb3JlIGRlc2NyaXB0aXZlIG5hbWU6IHRvdGFsXzxpbmdyZWRpZW50PgogICAgbmFtZXMocGVyX2dyb3VwZXIpW3doaWNoKG5hbWVzKHBlcl9ncm91cGVyKSA9PSAidG90YWwiKV0gPC0gcGFzdGUwKCJ0b3RhbF8iLCBpbmdyZWRpZW50X3dhbnQpCiAgICAKICAgIHJldHVybihwZXJfZ3JvdXBlcikKICB9CiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tICMgCiAgCiAgaW5ncmVkaWVudHNfcGVyX2dyb3VwZXIgPC0gZ2V0X2luZ3JlZGllbnRzX3Blcl9ncm91cGVyKGJlZXJfc3ByZWFkX3NlbGVjdGVkLCBncm91cGVyKQogIHJldHVybihpbmdyZWRpZW50c19wZXJfZ3JvdXBlcikKfQpgYGAKCgoqIE5vdyBydW4gdGhlIGZ1bmN0aW9uIHdpdGggYGluZ3JlZGllbnRfd2FudGAgYXMgZmlyc3QgaG9wcywgdGhlbiBtYWx0CiogVGhlbiBqb2luIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lcyBhbmQgcmVtb3ZlL3Jlb3JkZXIgc29tZSBjb2x1bW5zCgpgYGB7ciwgZWNobz1UUlVFLCBldmFsPVRSVUV9CiMgUnVuIHRoZSBlbnRpcmUgZnVuY3Rpb24gd2l0aCBpbmdyZWRpZW50X3dhbnQgc2V0IHRvIGhvcHMsIGdyb3VwaW5nIGJ5IG5hbWUKaW5ncmVkaWVudHNfcGVyX2JlZXJfaG9wcyA8LSBwaWNrX2luZ3JlZGllbnRfZ2V0X2JlZXIoaW5ncmVkaWVudF93YW50ID0gImhvcHMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmVlcl9uZWNlc3NpdGllcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwZXIgPSBjKCJuYW1lIiwgInN0eWxlX2NvbGxhcHNlZCIpKQoKIyBTYW1lIGZvciBtYWx0CmluZ3JlZGllbnRzX3Blcl9iZWVyX21hbHQgPC0gcGlja19pbmdyZWRpZW50X2dldF9iZWVyKGluZ3JlZGllbnRfd2FudCA9ICJtYWx0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJlZXJfbmVjZXNzaXRpZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cGVyID0gYygibmFtZSIsICJzdHlsZV9jb2xsYXBzZWQiKSkKCiMgSm9pbiB0aG9zZSBvbiBvdXIgb3JpZ2luYWwgZGF0YWZyYW1lIGJ5IG5hbWUKYmVlcl9pbmdyZWRpZW50c19qb2luX2ZpcnN0X2luZ3JlZGllbnQgPC0gbGVmdF9qb2luKGJlZXJfbmVjZXNzaXRpZXMsIGluZ3JlZGllbnRzX3Blcl9iZWVyX2hvcHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJuYW1lIikKYmVlcl9pbmdyZWRpZW50c19qb2luIDwtIGxlZnRfam9pbihiZWVyX2luZ3JlZGllbnRzX2pvaW5fZmlyc3RfaW5ncmVkaWVudCwgaW5ncmVkaWVudHNfcGVyX2JlZXJfbWFsdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJuYW1lIikKCgojIFRha2Ugb3V0IHNvbWUgdW5uZWNlc3NhcnkgY29sdW1ucwp1bm5lY2Vzc2FyeV9jb2xzIDwtIGMoInN0eWxlSWQiLCAiYWJ2X3NjYWxlZCIsICJpYnVfc2NhbGVkIiwgInNybV9zY2FsZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICJob3BzX2lkIiwgIm1hbHRfaWQiLCAiZ2xhc3N3YXJlSWQiLCAic3R5bGUuY2F0ZWdvcnlJZCIpCmJlZXJfaW5ncmVkaWVudHNfam9pbiA8LSBiZWVyX2luZ3JlZGllbnRzX2pvaW5bLCAoISBuYW1lcyhiZWVyX2luZ3JlZGllbnRzX2pvaW4pICVpbiUgdW5uZWNlc3NhcnlfY29scyldCgoKIyBJZiB3ZSBhbHNvIHdhbnQgdG8gdGFrZSBvdXQgYW55IG9mIHRoZSBtYWx0X25hbWVfMSwgbWFsdF9uYW1lXzIsIGV0Yy4gY29sdW1ucyB3ZSBjYW4gZG8gdGhpcyB3aXRoIGEgZ3JlcAptb3JlX3VubmVjZXNzYXJ5IDwtIGMoImhvcHNfbmFtZV98bWFsdF9uYW1lXyIpCmJlZXJfaW5ncmVkaWVudHNfam9pbiA8LSAKICBiZWVyX2luZ3JlZGllbnRzX2pvaW5bLCAoISBncmVwbChtb3JlX3VubmVjZXNzYXJ5LCBuYW1lcyhiZWVyX2luZ3JlZGllbnRzX2pvaW4pKSA9PSBUUlVFKV0KCiMgUmVvcmRlciBjb2x1bW5zIGEgYml0CmJlZXJfaW5ncmVkaWVudHNfam9pbiA8LSBiZWVyX2luZ3JlZGllbnRzX2pvaW4gJT4lIAogIHNlbGVjdCgKICAgIGlkLCBuYW1lLCB0b3RhbF9ob3BzLCB0b3RhbF9tYWx0LCBldmVyeXRoaW5nKCksIC1kZXNjcmlwdGlvbgogICkKCiMgQW5kIGdldCBhIGRmIHRoYXQgaW5jbHVkZXMgdG90YWxfaG9wcyBhbmQgdG90YWxfbWFsdCBidXQgbm90IGFsbCB0aGUgb3RoZXIgaW5ncmVkaWVudCBjb2x1bW5zCmJlZXJfdG90YWxzIDwtIGJlZXJfaW5ncmVkaWVudHNfam9pbiAlPiUgCiAgc2VsZWN0KAogICAgaWQsIG5hbWUsIHRvdGFsX2hvcHMsIHRvdGFsX21hbHQsIHN0eWxlLCBzdHlsZV9jb2xsYXBzZWQsCiAgICBhYnYsIGlidSwgc3JtLCBnbGFzcywgaG9wc19uYW1lLCBtYWx0X25hbWUKICApCgpgYGAKCgoKTm93IHdlJ3JlIGxlZnQgd2l0aCBzb21ldGhpbmcgb2YgYSBzcGFyc2UgbWF0cml4IG9mIGFsbCB0aGUgaW5ncmVkaWVudHMgY29tcGFyZWQgdG8gYWxsIHRoZSBiZWVycwpgYGB7cn0Ka2FibGUoYmVlcl9pbmdyZWRpZW50c19qb2luWzE6MjAsIF0pCmBgYAoKCgoKKioqCgojIyMgVW5zdXBlcnZpc2VkIENsdXN0ZXJpbmcgCldlIEstbWVhbnMgY2x1c3RlciBiZWVycyBiYXNlZCBvbiBjZXJ0YWluIG51bWVyaWMgcHJlZGljdG9yIHZhcmlhYmxlcy4gCgoKKipQcmVwKioKCiogV3JpdGUgYSBmdW5jaXRvbiB0aGF0IHRha2VzIGEgZGF0YWZyYW1lLCBhIHNldCBvZiBwcmVkaWN0b3JzLCBhIHJlc3BvbnNlIHZhcmlhYmxlLCBhbmQgdGhlIG51bWJlciBvZiBjbHVzdGVyIGNlbnRlcnMgeW91IHdhbnQKICAgICogTkI6IFRoZXJlIGFyZSBub3Qgbm90IHZlcnkgbWFueSBiZWVycyBoYXZlIFNSTSBzbyB3ZSBtYXkgbm90IHdhbnQgdG8gb21pdCBiYXNlZCBvbiBpdAoKKiBUYWtlIG91dCBtaXNzaW5nIHZhbHVlcywgYW5kIHNjYWxlIHRoZSBkYXRhCiogVGFrZSBvdXQgb3V0bGllcnMsIGRlZmluZWQgYXMgYmVlcnMgaGF2ZSB0byBoYXZlIGFuIEFCViBiZXR3ZWVuIDMgYW5kIDIwIGFuZCBhbiBJQlUgbGVzcyB0aGFuIDIwMAoqIFRoZW4gY2x1c3RlciBvbiBqdXN0IHRoZSBwcmVkaWN0b3JzIGFuZCBjb21wYXJlIHRvIHRoZSByZXNwb25zZSB2YXJpYWJsZQogIAoKYGBge3IsIGVjaG89VFJVRX0KCmxpYnJhcnkoTmJDbHVzdCkKCmNsdXN0ZXJfaXQgPC0gZnVuY3Rpb24oZGYsIHByZWRzLCB0b19zY2FsZSwgcmVzcCwgbl9jZW50ZXJzKSB7CiAgZGZfZm9yX2NsdXN0ZXJpbmcgPC0gZGYgJT4lCiAgICBzZWxlY3RfKC5kb3RzID0gYyhyZXNwb25zZV92YXJzLCBjbHVzdGVyX29uKSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBmaWx0ZXIoCiAgICAgIGFidiA8IDIwICYgYWJ2ID4gMwogICAgKSAlPiUKICAgIGZpbHRlcigKICAgICAgaWJ1IDwgMjAwCiAgICApCgogIGRmX2FsbF9wcmVkcyA8LSBkZl9mb3JfY2x1c3RlcmluZyAlPiUKICAgIHNlbGVjdF8oLmRvdHMgPSBwcmVkcykKCiAgZGZfcHJlZHNfc2NhbGUgPC0gZGZfYWxsX3ByZWRzICU+JQogICAgc2VsZWN0XyguZG90cyA9IHRvX3NjYWxlKSAlPiUKICAgIHJlbmFtZSgKICAgICAgYWJ2X3NjYWxlZCA9IGFidiwKICAgICAgaWJ1X3NjYWxlZCA9IGlidSwKICAgICAgc3JtX3NjYWxlZCA9IHNybQogICAgKSAlPiUKICAgIHNjYWxlKCkgJT4lCiAgICBhc190aWJibGUoKQoKICBkZl9wcmVkcyA8LSBiaW5kX2NvbHMoZGZfcHJlZHNfc2NhbGUsIGRmX2FsbF9wcmVkc1ssICghbmFtZXMoZGZfYWxsX3ByZWRzKSAlaW4lIHRvX3NjYWxlKV0pCgogIGRmX291dGNvbWUgPC0gZGZfZm9yX2NsdXN0ZXJpbmcgJT4lCiAgICBzZWxlY3RfKC5kb3RzID0gcmVzcCkgJT4lCiAgICBuYS5vbWl0KCkKCiAgc2V0LnNlZWQoOSkKICBjbHVzdGVyZWRfZGZfb3V0IDwtIGttZWFucyh4ID0gZGZfcHJlZHMsIGNlbnRlcnMgPSBuX2NlbnRlcnMsIHRyYWNlID0gVFJVRSkKCiAgY2x1c3RlcmVkX2RmIDwtIGFzX3RpYmJsZShkYXRhLmZyYW1lKAogICAgY2x1c3Rlcl9hc3NpZ25tZW50ID0gZmFjdG9yKGNsdXN0ZXJlZF9kZl9vdXQkY2x1c3RlciksCiAgICBkZl9vdXRjb21lLCBkZl9wcmVkcywKICAgIGRmX2Zvcl9jbHVzdGVyaW5nICU+JSBzZWxlY3QoYWJ2LCBpYnUsIHNybSkpKQoKICByZXR1cm4oY2x1c3RlcmVkX2RmKQp9CgpgYGAKCgoKIAoqKkNsdXN0ZXIqKgoKRmlyc3Qgd2UnbGwgcnVuIHRoZSBmdWN0aW9uIHdpdGggMTAgY2VudGVycywgYW5kIGNsdXN0ZXIgb24gdGhlIHByZWRpY3RvcnMgQUJWLCBJQlUsIFNSTSwgdG90YWxfaG9wcywgYW5kIHRvdGFsX21hbHQuCgoKYGBge3IsIGVjaG89VFJVRX0KCmNsdXN0ZXJfb24gPC0gYygiYWJ2IiwgImlidSIsICJzcm0iLCAidG90YWxfaG9wcyIsICJ0b3RhbF9tYWx0IikKdG9fc2NhbGUgPC0gYygiYWJ2IiwgImlidSIsICJzcm0iLCAidG90YWxfaG9wcyIsICJ0b3RhbF9tYWx0IikKcmVzcG9uc2VfdmFycyA8LSBjKCJuYW1lIiwgInN0eWxlIiwgInN0eWxlX2NvbGxhcHNlZCIpCgpjbHVzdGVyZWRfYmVlciA8LSBjbHVzdGVyX2l0KGRmID0gYmVlcl90b3RhbHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZHMgPSBjbHVzdGVyX29uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvX3NjYWxlID0gdG9fc2NhbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzcCA9IHJlc3BvbnNlX3ZhcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jZW50ZXJzID0gMTApCmBgYAoKCkhlYWQgb2YgdGhlIGNsdXN0ZXJpbmcgZGF0YQpgYGB7cn0Ka2FibGUoY2x1c3RlcmVkX2JlZXJbMToyMCwgXSkKYGBgCgoKCkEgdGFibGUgb2YgY2x1c3RlciBjb3VudHMgYnJva2VuIGRvd24gYnkgc3R5bGUKYGBge3J9CmNsdXN0ZXJfdGFibGVfY291bnRzIDwtIHRhYmxlKHN0eWxlID0gY2x1c3RlcmVkX2JlZXIkc3R5bGVfY29sbGFwc2VkLCBjbHVzdGVyID0gY2x1c3RlcmVkX2JlZXIkY2x1c3Rlcl9hc3NpZ25tZW50KQoKa2FibGUoY2x1c3Rlcl90YWJsZV9jb3VudHMpCmBgYAoKClBsb3QgdGhlIGNsdXN0ZXJzLiBUaGVyZSBhcmUgMyBheGVzOiBBQlYsIElCVSwgYW5kIFNSTSwgc28gd2UgY2hvb3NlIHR3byBhdCBhIHRpbWUuIAoKYGBge3IsIGVjaG89VFJVRX0KY2x1c3RlcmVkX2JlZXJfcGxvdF9hYnZfaWJ1IDwtIGdncGxvdChkYXRhID0gY2x1c3RlcmVkX2JlZXIsIGFlcyh4ID0gYWJ2LCB5ID0gaWJ1LCBjb2xvdXIgPSBjbHVzdGVyX2Fzc2lnbm1lbnQpKSArIAogIGdlb21faml0dGVyKCkgKyB0aGVtZV9taW5pbWFsKCkgICsKICBnZ3RpdGxlKCJrLU1lYW5zIENsdXN0ZXJpbmcgb2YgQmVlciBieSBBQlYsIElCVSwgU1JNIikgKwogIGxhYnMoeCA9ICJBQlYiLCB5ID0gIklCVSIpICsKICBsYWJzKGNvbG91ciA9ICJDbHVzdGVyIEFzc2lnbm1lbnQiKQpjbHVzdGVyZWRfYmVlcl9wbG90X2Fidl9pYnUKCmNsdXN0ZXJlZF9iZWVyX3Bsb3RfYWJ2X3NybSA8LSBnZ3Bsb3QoZGF0YSA9IGNsdXN0ZXJlZF9iZWVyLCBhZXMoeCA9IGFidiwgeSA9IHNybSwgY29sb3VyID0gY2x1c3Rlcl9hc3NpZ25tZW50KSkgKyAKICBnZW9tX2ppdHRlcigpICsgdGhlbWVfbWluaW1hbCgpICArCiAgZ2d0aXRsZSgiay1NZWFucyBDbHVzdGVyaW5nIG9mIEJlZXIgYnkgQUJWLCBJQlUsIFNSTSIpICsKICBsYWJzKHggPSAiQUJWIiwgeSA9ICJTUk0iKSArCiAgbGFicyhjb2xvdXIgPSAiQ2x1c3RlciBBc3NpZ25tZW50IikKY2x1c3RlcmVkX2JlZXJfcGxvdF9hYnZfc3JtCmBgYAoKCgpOb3cgd2UgY2FuIGFkZCBpbiB0aGUgc3R5bGUgY2VudGVycyAobWVhbnMpIGZvciBlYWNoIGBzdHlsZV9jb2xsYXBzZWRgIGFuZCBsYWJlbCBpdC4KCmBgYHtyLCBlY2hvPVRSVUV9CmxpYnJhcnkoZ2dyZXBlbCkKYWJ2X2lidV9jbHVzdGVyc192c19zdHlsZV9jZW50ZXJzIDwtIGdncGxvdCgpICsgICAKICBnZW9tX3BvaW50KGRhdGEgPSBjbHVzdGVyZWRfYmVlciwgCiAgICAgICAgICAgICBhZXMoeCA9IGFidiwgeSA9IGlidSwgY29sb3VyID0gY2x1c3Rlcl9hc3NpZ25tZW50KSwgYWxwaGEgPSAwLjUpICsKICBnZW9tX3BvaW50KGRhdGEgPSBzdHlsZV9jZW50ZXJzLAogICAgICAgICAgICAgYWVzKG1lYW5fYWJ2LCBtZWFuX2lidSksIGNvbG91ciA9ICJibGFjayIpICsKICBnZW9tX3RleHRfcmVwZWwoZGF0YSA9IHN0eWxlX2NlbnRlcnMsIGFlcyhtZWFuX2FidiwgbWVhbl9pYnUsIGxhYmVsID0gc3R5bGVfY29sbGFwc2VkKSwgCiAgICAgICAgICAgICAgICAgIGJveC5wYWRkaW5nID0gdW5pdCgwLjQ1LCAibGluZXMiKSwKICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gIkNhbGlicmkiLAogICAgICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4zKSArCiAgZ2d0aXRsZSgiUG9wdWxhciBTdHlsZXMgdnMuIGstTWVhbnMgQ2x1c3RlcmluZyBvZiBCZWVyIGJ5IEFCViwgSUJVLCBTUk0iKSArCiAgbGFicyh4ID0gIkFCViIsIHkgPSAiSUJVIikgKwogIGxhYnMoY29sb3VyID0gIkNsdXN0ZXIgQXNzaWdubWVudCIpICsKICB0aGVtZV9idygpCmFidl9pYnVfY2x1c3RlcnNfdnNfc3R5bGVfY2VudGVycwpgYGAKCgpUaGUgY2x1c3RlcmluZyBhYm92ZSB1c2VkIGEgc21hbGxlciBudW1iZXIgb2YgY2x1c3RlcnMgKDEwKSB0aGFuIHRoZXJlIGFyZSBgc3R5bGVzX2NvbGxhcHNlZGAuIFRoYXQgbWFrZXMgaXQgZGlmZmljdWx0IHRvIGRldGVybWluZSB3aGV0aGVyIGEgZ2l2ZW4gc3R5bGUgZml0cyBzbnVnbHkgaW50byBhIGNsdXN0ZXIgb3Igbm90LgoKCgoqKkNsdXN0ZXIgb24ganVzdCBjZXJ0YWluIHNlbGVjdGVkIHN0eWxlcyoqCgpXZSdsbCB0YWtlIGZpdmUgdmVyeSBkaXN0aW5jdCBjb2xsYXBzZWQgc3R5bGVzIGFuZCByZS1ydW4gdGhlIGNsdXN0ZXJpbmcgb24gYmVlcnMgdGhhdCBmYWxsIGludG8gdGhlc2UgY2F0ZWdvcmllcy4gClRoZXNlIHN0eWxlcyB3ZXJlIGludGVudGlvbmFsbHkgY2hvc2VuIGJlY2F1c2UgdGhleSBhcmUgcXVpdGUgZGlzdGluY3Q6IEJsb25kZSwgSVBBLCBTdG91dCwgVHJpcGVsLCBXaGVhdC4gQXJndWFibHksIG9mIHRoZXNlIGZpdmUgc3R5bGVzIEJsb25kZXMgYW5kIFdoZWF0cyBhcmUgdGhlIGNsb3Nlc3QKCgoKYGBge3IsIGVjaG89VFJVRX0Kc3R5bGVzX3RvX2tlZXAgPC0gYygiQmxvbmRlIiwgIkluZGlhIFBhbGUgQWxlIiwgIlN0b3V0IiwgIlRyaXBlbCIsICJXaGVhdCIpCmJ0X2NlcnRhaW5fc3R5bGVzIDwtIGJlZXJfdG90YWxzICU+JQogIGZpbHRlcigKICAgIHN0eWxlX2NvbGxhcHNlZCAlaW4lIHN0eWxlc190b19rZWVwCiAgKQoKCmNsdXN0ZXJfb24gPC0gYygiYWJ2IiwgImlidSIsICJzcm0iLCAidG90YWxfaG9wcyIsICJ0b3RhbF9tYWx0IikKdG9fc2NhbGUgPC0gYygiYWJ2IiwgImlidSIsICJzcm0iLCAidG90YWxfaG9wcyIsICJ0b3RhbF9tYWx0IikKcmVzcG9uc2VfdmFycyA8LSBjKCJuYW1lIiwgInN0eWxlIiwgInN0eWxlX2NvbGxhcHNlZCIpCgpjZXJ0YWluX3N0eWxlc19jbHVzdGVyZWQgPC0gY2x1c3Rlcl9pdChkZiA9IGJ0X2NlcnRhaW5fc3R5bGVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkcyA9IGNsdXN0ZXJfb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvX3NjYWxlID0gdG9fc2NhbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3AgPSByZXNwb25zZV92YXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2NlbnRlcnMgPSA1KQoKc3R5bGVfY2VudGVyc19jZXJ0YWluX3N0eWxlcyA8LSBzdHlsZV9jZW50ZXJzICU+JSAKICBmaWx0ZXIoc3R5bGVfY29sbGFwc2VkICVpbiUgc3R5bGVzX3RvX2tlZXApCmBgYAoKCgoKClRhYmxlIG9mIHN0eWxlIHZzLiBjbHVzdGVyLgpgYGB7cn0Ka2FibGUodGFibGUoc3R5bGUgPSBjZXJ0YWluX3N0eWxlc19jbHVzdGVyZWQkc3R5bGVfY29sbGFwc2VkLCBjbHVzdGVyID0gY2VydGFpbl9zdHlsZXNfY2x1c3RlcmVkJGNsdXN0ZXJfYXNzaWdubWVudCkpCmBgYAoKCgpOb3cgdGhhdCB3ZSBoYXZlIGEgbWFuYWdlYWJsZSBudW1iZXIgb2Ygc3R5bGVzLCB3ZSBjYW4gc2VlIGhvdyB3ZWxsIGZpdCBlYWNoIGNsdXN0ZXIgaXMgdG8gZWFjaCBzdHlsZS4gSWYgdGhlIGZlYXR1cmVzIHdlIGNsdXN0ZXJlZCBvbiBwZXJmZWN0bHkgcHJlZGljdGVkIHN0eWxlLCB0aGVyZSB3b3VsZCBlYWNoIGNvbG9yIChjbHVzdGVyKSB3b3VsZCBiZSB1bmlxdWUgdG8gZWFjaCBmYWNldCBvZiB0aGUgcGxvdC4gKEUuZy4sIGxlZnQgZW50aXJlbHkgYmx1ZSwgc2Vjb25kIGZyb20gbGVmdCBlbnRpcmVseSBncmVlbiwgZXRjLikKCgoKYGBge3IsIGVjaG89VFJVRX0KYnlfc3R5bGVfcGxvdCA8LSBnZ3Bsb3QoKSArICAgCiAgZ2VvbV9wb2ludChkYXRhID0gY2VydGFpbl9zdHlsZXNfY2x1c3RlcmVkLCAKICAgICAgICAgICAgIGFlcyh4ID0gYWJ2LCB5ID0gaWJ1LAogICAgICAgICAgICAgICAgIGNvbG91ciA9IGNsdXN0ZXJfYXNzaWdubWVudCksIGFscGhhID0gMC41KSArCiAgZmFjZXRfZ3JpZCguIH4gc3R5bGVfY29sbGFwc2VkKSArCiAgZ2VvbV9wb2ludChkYXRhID0gc3R5bGVfY2VudGVyc19jZXJ0YWluX3N0eWxlcywKICAgICAgICAgICBhZXMobWVhbl9hYnYsIG1lYW5faWJ1KSwgY29sb3VyID0gImJsYWNrIiwgc2hhcGUgPSA1KSArCiAgZ2d0aXRsZSgiU2VsZWN0ZWQgU3R5bGVzIENsdXN0ZXIgQXNzaWdubWVudCIpICsKICBsYWJzKHggPSAiQUJWIiwgeSA9ICJJQlUiKSArCiAgbGFicyhjb2xvdXIgPSAiQ2x1c3RlciIpICsKICB0aGVtZV9idygpCmJ5X3N0eWxlX3Bsb3QKYGBgCgoKCgo8IS0tICMjIyBCYWNrIHRvIGNsdXN0ZXJpbmc6IGNsdXN0ZXIgb24gb25seSA1IHN0eWxlcyAtLT4KCjwhLS0gKiBXZSdsbCBwYXJlIGRvd24gdGhlIGJlZXIgZGF0YSB0byBqdXN0IGJlZXJzIGluIDUgc2VsZWN0ZWQgc3R5bGVzOyAKPCEtLSAqIFdlJ2xsIGNsdXN0ZXIgdGhlc2UgaW50byA1IGNsdXN0ZXJzIC0tPgo8IS0tIDwhLS0gKiBUaGlzIHRpbWUgd2UnbGwgYWRkIGluIGB0b3RhbF9ob3BzYCBhbmQgYHRvdGFsX21hbHRgIGFzIHByZWRpY3RvcnMgIC0tPiAtLT4KCgoKCjwhLS0gZ2dwbG90KCkgKyAtLT4KPCEtLSAgIGdlb21fcG9pbnQoZGF0YSA9IGNlcnRhaW5fc3R5bGVzX2NsdXN0ZXJlZCwgLS0+CjwhLS0gICAgICAgICAgICAgIGFlcyh4ID0gYWJ2LCB5ID0gaWJ1LCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgIHNoYXBlID0gY2x1c3Rlcl9hc3NpZ25tZW50LCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgIGNvbG91ciA9IHN0eWxlX2NvbGxhcHNlZCksIGFscGhhID0gMC41KSArIC0tPgo8IS0tICAgZ2VvbV9wb2ludChkYXRhID0gc3R5bGVfY2VudGVyc19jZXJ0YWluX3N0eWxlcywgLS0+CjwhLS0gICAgICAgICAgICAgIGFlcyhtZWFuX2FidiwgbWVhbl9pYnUpLCBjb2xvdXIgPSAiYmxhY2siKSArIC0tPgo8IS0tICAgZ2VvbV90ZXh0X3JlcGVsKGRhdGEgPSBzdHlsZV9jZW50ZXJzX2NlcnRhaW5fc3R5bGVzLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICBhZXMobWVhbl9hYnYsIG1lYW5faWJ1LCBsYWJlbCA9IHN0eWxlX2NvbGxhcHNlZCksIC0tPgo8IS0tICAgICAgICAgICAgICAgICAgIGJveC5wYWRkaW5nID0gdW5pdCgwLjQ1LCAibGluZXMiKSwgLS0+CjwhLS0gICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gIkNhbGlicmkiLCAtLT4KPCEtLSAgICAgICAgICAgICAgICAgICBsYWJlbC5zaXplID0gMC4zKSArIC0tPgo8IS0tICAgZ2d0aXRsZSgiU2VsZWN0ZWQgU3R5bGVzIChjb2xvcnMpIG1hdGNoZWQgd2l0aCBDbHVzdGVyIEFzc2lnbm1lbnRzIChzaGFwZXMpIikgKyAtLT4KPCEtLSAgIGxhYnMoeCA9ICJBQlYiLCB5ID0gIklCVSIpICsgLS0+CjwhLS0gICBsYWJzKGNvbG91ciA9ICJTdHlsZSIsIHNoYXBlID0gIkNsdXN0ZXIgQXNzaWdubWVudCIpICsgLS0+CjwhLS0gICB0aGVtZV9idygpIC0tPgoKPCEtLSBgYGAgLS0+CgoKCiMjIFJhbmRvbSBhc2lkZXMgaW50byBob3BzCgoqKkRvIG1vcmUgaG9wcyBhbHdheXMgbWVhbiBtb3JlIGJpdHRlcm5lc3M/KioKCiogSXQgd291bGQgYXBwZWFyIHNvLCBmcm9tIHRoaXMgZ3JhcGggYW5kIHRoaXMgcmVncmVzc2lvbiAoYmV0YSA9IDIuMzk0NDE4KQpgYGB7ciwgZWNobz1UUlVFfQoKZ2dwbG90KGRhdGEgPSBiZWVyX2luZ3JlZGllbnRzX2pvaW4sIGFlcyh0b3RhbF9ob3BzLCBpYnUpKSArCiAgZ2VvbV9wb2ludChhZXModG90YWxfaG9wcywgaWJ1LCBjb2xvdXIgPSBzdHlsZV9jb2xsYXBzZWQpKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0sIHNlID0gRkFMU0UsIGNvbG91ciA9ICJibGFjayIpICsgCiAgZ2d0aXRsZSgiSG9wcyBQZXIgQmVlciB2cy4gQml0dGVybmVzcyIpICsKICBsYWJzKHggPSAiTnVtYmVyIG9mIEhvcHMiLCB5ID0gIklCVSIsIGNvbG91ciA9ICJTdHlsZSBDb2xsYXBzZWQiKSArCiAgdGhlbWVfbWluaW1hbCgpCgpob3BzX2lidV9sbSA8LSBsbShpYnUgfiB0b3RhbF9ob3BzLCBkYXRhID0gYmVlcl9pbmdyZWRpZW50c19qb2luKQpzdW1tYXJ5KGhvcHNfaWJ1X2xtKQpgYGAKCiogSG93ZXZlciwgcGFzdCBhIGNlcnRhaW4gcG9pbnQgKDMgaG9wcyBvciBtb3JlKSwgdGhlcmUncyBubyBlZmZlY3Qgb2YgbnVtYmVyIG9mIGhvcHMgb24gSUJVCmBgYHtyLCBlY2hvPVRSVUV9CmdncGxvdChkYXRhID0gYmVlcl9pbmdyZWRpZW50c19qb2luW3doaWNoKGJlZXJfaW5ncmVkaWVudHNfam9pbiR0b3RhbF9ob3BzID4gMgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIGJlZXJfaW5ncmVkaWVudHNfam9pbiR0b3RhbF9ob3BzIDwgOCksIF0sIGFlcyh0b3RhbF9ob3BzLCBpYnUpKSArCiAgZ2VvbV9wb2ludChhZXModG90YWxfaG9wcywgaWJ1LCBjb2xvdXIgPSBzdHlsZV9jb2xsYXBzZWQpKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG0sIHNlID0gRkFMU0UsIGNvbG91ciA9ICJibGFjayIpICsKICBnZ3RpdGxlKCIzKyBIb3BzIFBlciBCZWVyIHZzLiBCaXR0ZXJuZXNzIikgKwogIGxhYnMoeCA9ICJOdW1iZXIgb2YgSG9wcyIsIHkgPSAiSUJVIiwgY29sb3VyID0gIlN0eWxlIENvbGxhcHNlZCIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKKipNb3N0IHBvcHVsYXIgaG9wcyoqCgpgYGB7ciwgZWNobz1UUlVFfQojIEdhdGhlciB1cCBhbGwgdGhlIGhvcHMgY29sdW1ucyBpbnRvIG9uZSBjYWxsZWQgYGhvcF9uYW1lYApiZWVyX25lY2Vzc2l0aWVzX2hvcHNfZ2F0aGVyZWQgPC0gYmVlcl9uZWNlc3NpdGllcyAlPiUKICBnYXRoZXIoCiAgICBob3Bfa2V5LCBob3BfbmFtZSwgaG9wc19uYW1lXzE6aG9wc19uYW1lXzEzCiAgKSAlPiUgYXNfdGliYmxlKCkKCiMgRmlsdGVyIHRvIGp1c3QgdGhvc2UgYmVlcnMgdGhhdCBoYXZlIGF0IGxlYXN0IG9uZSBob3AKYmVlcl9uZWNlc3NpdGllc193X2hvcHMgPC0gYmVlcl9uZWNlc3NpdGllc19ob3BzX2dhdGhlcmVkICU+JSAKICBmaWx0ZXIoIWlzLm5hKGhvcF9uYW1lKSkgJT4lIAogIGZpbHRlcighaG9wX25hbWUgPT0gIiIpCgpiZWVyX25lY2Vzc2l0aWVzX3dfaG9wcyRob3BfbmFtZSA8LSBmYWN0b3IoYmVlcl9uZWNlc3NpdGllc193X2hvcHMkaG9wX25hbWUpCgojIEZvciBhbGwgaG9wcywgZmluZCB0aGUgbnVtYmVyIG9mIGJlZXJzIHRoZXkncmUgaW4gYXMgd2VsbCBhcyB0aG9zZSBiZWVycycgbWVhbiBJQlUgYW5kIEFCVgpob3BzX2JlZXJfc3RhdHMgPC0gYmVlcl9uZWNlc3NpdGllc193X2hvcHMgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZ3JvdXBfYnkoaG9wX25hbWUpICU+JSAKICBzdW1tYXJpc2UoCiAgICBtZWFuX2lidSA9IG1lYW4oaWJ1LCBuYS5ybSA9IFRSVUUpLCAKICAgIG1lYW5fYWJ2ID0gbWVhbihhYnYsIG5hLnJtID0gVFJVRSksCiAgICBuID0gbigpCiAgKQoKIyBQYXJlIHRvIGhvcHMgdGhhdCBhcmUgdXNlZCBpbiBhdCBsZWFzdCA1MCBiZWVycwpwb3BfaG9wc19iZWVyX3N0YXRzIDwtIGhvcHNfYmVlcl9zdGF0c1tob3BzX2JlZXJfc3RhdHMkbiA+IDUwLCBdCmthYmxlKHBvcF9ob3BzX2JlZXJfc3RhdHMpCgojIEtlZXAganVzdCBiZWVycyB0aGF0IGNvbnRhaW4gdGhlc2UgbW9zdCBwb3B1bGFyIGhvcHMKYmVlcl9uZWNlc3NpdGllc193X3BvcHVsYXJfaG9wcyA8LSBiZWVyX25lY2Vzc2l0aWVzX3dfaG9wcyAlPiUgCiAgZmlsdGVyKGhvcF9uYW1lICVpbiUgcG9wX2hvcHNfYmVlcl9zdGF0cyRob3BfbmFtZSkgJT4lIAogIGRyb3BsZXZlbHMoKSAKYGBgCgpBcmUgdGhlcmUgY2VydGlhbiBob3BzIHRoYXQgYXJlIHVzZWQgbW9yZSBvZnRlbiBpbiB2ZXJ5IGhpZ2ggSUJVIG9yIEFCViBiZWVycz8KSGFyZCB0byBkZXRlY3QgYSBwYXR0ZXJuCmBgYHtyLCBlY2hvID0gVFJVRX0KZ2dwbG90KGRhdGEgPSBiZWVyX25lY2Vzc2l0aWVzX3dfcG9wdWxhcl9ob3BzKSArIAogIGdlb21fcG9pbnQoYWVzKGFidiwgaWJ1LCBjb2xvdXIgPSBob3BfbmFtZSkpICsKICBnZ3RpdGxlKCJCZWVycyBDb250YWluaW5nIG1vc3QgUG9wdWxhciBIb3BzIikgKwogIGxhYnMoeCA9ICJBQlYiLCB5ID0gIklCVSIsIGNvbG91ciA9ICJIb3AgTmFtZSIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7ciwgZWNobz1UUlVFfQpnZ3Bsb3QoZGF0YSA9IHBvcF9ob3BzX2JlZXJfc3RhdHMpICsgCiAgZ2VvbV9wb2ludChhZXMobWVhbl9hYnYsIG1lYW5faWJ1LCBjb2xvdXIgPSBob3BfbmFtZSwgc2l6ZSA9IG4pKSArCiAgZ2d0aXRsZSgiTW9zdCBQb3B1bGFyIEhvcHMnIEVmZmVjdCBvbiBBbGNvaG9sIGFuZCBCaXR0ZXJuZXNzIikgKwogIGxhYnMoeCA9ICJNZWFuIEFCViBwZXIgSG9wIFR5cGUiLCB5ID0gIk1lYW4gSUJVIHBlciBIb3AgVHlwZSIsIGNvbG91ciA9ICJIb3AgTmFtZSIsIAogICAgICAgc2l6ZSA9ICJOdW1iZXIgb2YgQmVlcnMiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKCiMgTmV1cmFsIE5ldAoKKiBDYW4gQUJWLCBJQlUsIGFuZCBTUk0gYmUgdXNlZCBpbiBhIG5ldXJhbCBuZXQgdG8gcHJlZGljdCBgc3R5bGVgIG9yIGBzdHlsZV9jb2xsYXBzZWRgPwoqIEluIHRoZSBmdW5jdGlvbiwgc3BlY2lmeSB0aGUgZGF0YWZyYW1lIGFuZCB0aGUgb3V0Y29tZSwgZWl0aGVyIGBzdHlsZWAgb3IgYHN0eWxlX2NvbGxhcHNlZGA7IHRoZSBvbmUgbm90IHNwZWNpZmllZCBhcyBgb3V0Y29tZWAgd2lsbCBiZSBkcm9wcGVkCiogVGhlIHByZWRpY3RvciBjb2x1bW5zIHdpbGwgYmUgZXZlcnl0aGluZyBub3Qgc3BlY2lmaWVkIGluIHRoZSB2ZWN0b3IgYHByZWRpY3Rvcl92YXJzYAoqIFRoZSBmdW5jdGlvbiByZXR1cm5zIHRoZSBvdXRjb21lIHZhcmlhYmxlIHNsZWVjdGVkLCBuZXVyYWwgbmV0IG91dHB1dCwgdmFyaWFibGUgaW1wb3J0YW5jZSwgdGhlIHByZWRpY3Rpb24gZGF0YWZyYW1lLCBwcmVkaWN0aW9ucywgYW5kIGFjY3VyYWN5CgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZWNobz1UUlVFLCBldmFsPVRSVUUsIG1lc3NhZ2U9RkFMU0V9CgpsaWJyYXJ5KG5uZXQpCmxpYnJhcnkoY2FyZXQpCgpydW5fbmV1cmFsX25ldCA8LSBmdW5jdGlvbihkZiwgb3V0Y29tZSwgcHJlZGljdG9yX3ZhcnMpIHsKICBvdXQgPC0gbGlzdChvdXRjb21lID0gb3V0Y29tZSkKICAKICAjIENyZWF0ZSBhIG5ldyBjb2x1bW4gb3V0Y29tZTsgaXQncyBzdHlsZV9jb2xsYXBzZWQgaWYgeW91IHNldCBvdXRjb21lIHRvIHN0eWxlX2NvbGxhcHNlZCwgYW5kIHN0eWxlIG90aGVyd2lzZQogIGlmIChvdXRjb21lID09ICJzdHlsZV9jb2xsYXBzZWQiKSB7CiAgICBkZltbIm91dGNvbWUiXV0gPC0gZGZbWyJzdHlsZV9jb2xsYXBzZWQiXV0KICB9IGVsc2UgewogICAgZGZbWyJvdXRjb21lIl1dIDwtIGRmW1sic3R5bGUiXV0KICB9CgogIGRmJG91dGNvbWUgPC0gZmFjdG9yKGRmJG91dGNvbWUpCiAgCiAgY29sc190b19rZWVwIDwtIGMoIm91dGNvbWUiLCBwcmVkaWN0b3JfdmFycykKICAKICBkZiA8LSBkZiAlPiUKICAgIHNlbGVjdF8oLmRvdHMgPSBjb2xzX3RvX2tlZXApICU+JQogICAgbXV0YXRlKHJvdyA9IDE6bnJvdyhkZikpICU+JSAKICAgIGRyb3BsZXZlbHMoKQoKICAjIFNlbGVjdCA4MCUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nCiAgZGZfdHJhaW4gPC0gc2FtcGxlX24oZGYsIG5yb3coZGYpKigwLjgpKQogIAogICMgVGhlIHJlc3QgaXMgZm9yIHRlc3RpbmcKICBkZl90ZXN0IDwtIGRmICU+JQogICAgZmlsdGVyKCEgKHJvdyAlaW4lIGRmX3RyYWluJHJvdykpICU+JQogICAgc2VsZWN0KC1yb3cpCiAgCiAgZGZfdHJhaW4gPC0gZGZfdHJhaW4gJT4lCiAgICBzZWxlY3QoLXJvdykKICAKICAjIEJ1aWxkIG11bHRpbm9tYWlsIG5ldXJhbCBuZXQKICBubiA8LSBtdWx0aW5vbShvdXRjb21lIH4gLiwKICAgICAgICAgICAgICAgICBkYXRhID0gZGZfdHJhaW4sIG1heGl0PTUwMCwgdHJhY2U9VCkKCiAgIyBXaGljaCB2YXJpYWJsZXMgYXJlIHRoZSBtb3N0IGltcG9ydGFudCBpbiB0aGUgbmV1cmFsIG5ldD8KICBtb3N0X2ltcG9ydGFudF92YXJzIDwtIHZhckltcChubikKCiAgIyBIb3cgYWNjdXJhdGUgaXMgdGhlIG1vZGVsPyBDb21wYXJlIHByZWRpY3Rpb25zIHRvIG91dGNvbWVzIGZyb20gdGVzdCBkYXRhCiAgbm5fcHJlZHMgPC0gcHJlZGljdChubiwgdHlwZT0iY2xhc3MiLCBuZXdkYXRhID0gZGZfdGVzdCkKICBubl9hY2N1cmFjeSA8LSBwb3N0UmVzYW1wbGUoZGZfdGVzdCRvdXRjb21lLCBubl9wcmVkcykKCiAgb3V0IDwtIGxpc3Qob3V0LCBubiA9IG5uLCBtb3N0X2ltcG9ydGFudF92YXJzID0gbW9zdF9pbXBvcnRhbnRfdmFycywKICAgICAgICAgICAgICBkZl90ZXN0ID0gZGZfdGVzdCwKICAgICAgICAgICAgICBubl9wcmVkcyA9IG5uX3ByZWRzLAogICAgICAgICAgIG5uX2FjY3VyYWN5ID0gbm5fYWNjdXJhY3kpCgogIHJldHVybihvdXQpCn0KCmBgYAoKKiBTZXQgdGhlIGRhdGFmcmFtZSB0byBiZSBgYmVlcl90b3RhbHNgLCB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcyB0byBiZSB0aGUgdmVjdG9yIGNvbnRhaW5lZCBpbiBgcF92YXJzYCwgdGhlIG91dGNvbWUgdG8gYmUgYHN0eWxlX2NvbGxhcHNlZGAKCmBgYHtyLCBlY2hvPVRSVUUsIGV2YWw9VFJVRX0KcF92YXJzIDwtIGMoInRvdGFsX2hvcHMiLCAidG90YWxfbWFsdCIsICJhYnYiLCAiaWJ1IiwgInNybSIpCgpubl9jb2xsYXBzZWRfb3V0IDwtIHJ1bl9uZXVyYWxfbmV0KGRmID0gYmVlcl90b3RhbHMsIG91dGNvbWUgPSAic3R5bGVfY29sbGFwc2VkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkaWN0b3JfdmFycyA9IHBfdmFycykKCgojIEhvdyBhY2N1cmF0ZSB3YXMgaXQ/Cm5uX2NvbGxhcHNlZF9vdXQkbm5fYWNjdXJhY3kKCiMgV2hhdCB3ZXJlIHRoZSBtb3N0IGltcG9ydGFudCB2YXJpYWJsZXM/Cm5uX2NvbGxhcHNlZF9vdXQkbW9zdF9pbXBvcnRhbnRfdmFycwoKYGBgCgoKKiBXaGF0IGlmIHdlIHByZWRjaXQgYHN0eWxlYCBpbnN0ZWFkIG9mIGBzdHlsZV9jb2xsYXBzZWRgPwoKYGBge3IsIGVjaG89VFJVRX0KCm5uX25vdGNvbGxhcHNlZF9vdXQgPC0gcnVuX25ldXJhbF9uZXQoZGYgPSBiZWVyX3RvdGFscywgb3V0Y29tZSA9ICJzdHlsZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdG9yX3ZhcnMgPSBwX3ZhcnMpCgpubl9ub3Rjb2xsYXBzZWRfb3V0JG5uX2FjY3VyYWN5Cgpubl9ub3Rjb2xsYXBzZWRfb3V0JG1vc3RfaW1wb3J0YW50X3ZhcnMKCmBgYAoKCkFuZCBub3cgaWYgd2UgYWRkIGBnbGFzc2AgYXMgYSBwcmVkaWN0b3I/CmBgYHtyLCBlY2hvPVRSVUV9CgpwX3ZhcnNfYWRkX2dsYXNzIDwtIGMoInRvdGFsX2hvcHMiLCAidG90YWxfbWFsdCIsICJhYnYiLCAiaWJ1IiwgInNybSIpCgpubl9jb2xsYXBzZWRfb3V0X2FkZF9nbGFzcyA8LSBydW5fbmV1cmFsX25ldChkZiA9IGJlZXJfaW5ncmVkaWVudHNfam9pbiwgb3V0Y29tZSA9ICJzdHlsZV9jb2xsYXBzZWQiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3Rvcl92YXJzID0gcF92YXJzX25vX2dsYXNzKQoKbm5fY29sbGFwc2VkX291dF9hZGRfZ2xhc3Mkbm5fYWNjdXJhY3kKCm5uX2NvbGxhcHNlZF9vdXRfYWRkX2dsYXNzJG1vc3RfaW1wb3J0YW50X3ZhcnMKCmBgYAoKCgoKIyMjIFJhbmRvbSBmb3Jlc3Qgd2l0aCBhbGwgaW5ncmVkaWVudHMKCiogV2UgY2FuIHVzZSBhIHJhbmRvbSBmb3Jlc3QgdG8gZ2V0IGV2ZW4gbW9yZSBncmFudWxhciB3aXRoIGluZ3JlZGllbnRzCiAgICAqIFRoZSBzcGFyc2UgaW5ncmVkaWVudCBkYXRhZnJhbWUgd2FzIHRvbyBjb21wbGV4IGZvciB0aGUgbXVsdGlub21pYWwgbmV1cmFsIG5ldDsgaG93ZXZlciwgd2UgY2FuIAoKKiBIZXJlIHdlIGRvbid0IGluY2x1ZGUgYGdsYXNzYCBhcyBhIHByZWRpY3RvcgoKYGBge3IsIGVjaG89VFJVRX0KCmxpYnJhcnkocmFuZ2VyKQpsaWJyYXJ5KHN0cmluZ3IpCgpiaSA8LSBiZWVyX2luZ3JlZGllbnRzX2pvaW4gJT4lIAogIHNlbGVjdCgtYyhpZCwgbmFtZSwgY2x1c3Rlcl9hc3NpZ25tZW50LCBzdHlsZSwgaG9wc19uYW1lLCBtYWx0X25hbWUsCiAgICAgICAgICAgIGdsYXNzKSkgJT4lIAogIG11dGF0ZShyb3cgPSAxOm5yb3coLikpIAoKYmkkc3R5bGVfY29sbGFwc2VkIDwtIGZhY3RvcihiaSRzdHlsZV9jb2xsYXBzZWQpCgoKIyByYW5nZXIgY29tcGxhaW5zIGFib3V0IHNwZWNpYWwgY2hhcmFjdGVycyBhbmQgc3BhY2VzIGluIGluZ3JlZGllbnQgY29sdW1uIG5hbWVzLiBUYWtlIHRoZW0gb3V0IGFuZCByZXBsYWNlIHdpdGggZW1wdHkgc3RyaW5nLgpuYW1lcyhiaSkgPC0gdG9sb3dlcihuYW1lcyhiaSkpCm5hbWVzKGJpKSA8LSBzdHJfcmVwbGFjZV9hbGwobmFtZXMoYmkpLCAiICIsICIiKQpuYW1lcyhiaSkgPC0gc3RyX3JlcGxhY2VfYWxsKG5hbWVzKGJpKSwgIihbXFwoXFwpLVxcLycpXSspIiwgIiIpCgojIEtlZXAgODAlIGZvciB0cmFpbmluZwpiaV90cmFpbiA8LSBzYW1wbGVfbihiaSwgbnJvdyhiaSkqKDAuOCkpCgojIFRoZSByZXN0IGlzIGZvciB0ZXN0aW5nCmJpX3Rlc3QgPC0gYmkgJT4lCiAgZmlsdGVyKCEgKHJvdyAlaW4lIGJpX3RyYWluJHJvdykpICU+JQogIGRwbHlyOjpzZWxlY3QoLXJvdykKCmJpX3RyYWluIDwtIGJpX3RyYWluICU+JQogIGRwbHlyOjpzZWxlY3QoLXJvdykKCgpiaV9yZiA8LSByYW5nZXIoc3R5bGVfY29sbGFwc2VkIH4gLiwgZGF0YSA9IGJpX3RyYWluLCBpbXBvcnRhbmNlID0gImltcHVyaXR5Iiwgc2VlZCA9IDExKQpgYGAKCgpPT0IgKG91dCBvZiBiYWcpIHByZWRpY3Rpb24gZXJyb3IgaXMgYXJvdW5kIDU4JQogICAgKiBUaGlzIGNhbGN1bGF0ZWQgZnJvbSB0cmVlIHNhbXBsZXMgY29uc3RydWN0ZWQgYnV0IG5vdCB1c2VkIGluIHRyYWluaW5nIHNldDsgdGhlc2UgdHJlZXMgYmVjb21lIGVmZmVjdGl2ZWx5IHBhcnQgb2YgdGVzdCBzZXQKYGBge3J9CmJpX3JmCmBgYAoKCldlIGNhbiBjb21wYXJlIHByZWRpY3RlZCBjbGFzc2lmaWNhdGlvbiBvbiB0aGUgdGVzdCBzZXQgdG8gdGhlaXIgYWN0dWFsIHN0eWxlIGNsYXNzaWZpY2F0aW9uLgpgYGB7ciwgZWNobz1UUlVFfQpwcmVkX2JpX3JmIDwtIHByZWRpY3QoYmlfcmYsIGRhdCA9IGJpX3Rlc3QpCnRhYmxlKGJpX3Rlc3Qkc3R5bGVfY29sbGFwc2VkLCBwcmVkX2JpX3JmJHByZWRpY3Rpb25zKQpgYGAKCgpWYXJpYWJsZSBpbXBvcnRhbmNlCiogSW50ZXJlc3RpbmdseSwgQUJWLCBJQlUsIGFuZCBTUk0gYXJlIGFsbCBtdWNoIG1vcmUgaW1wb3J0YW50IGluIHRoZSByYW5kb20gZm9yZXN0IHRoYW4gYHRvdGFsX2hvcHNgIGFuZCBgdG90YWxfbWFsdGAKYGBge3IsIGVjaG89VFJVRX0KCmltcG9ydGFuY2UoYmlfcmYpWzE6MTBdCmBgYAoKCkhvdyBkb2VzIGEgQ1NSRiAoY2FzZS1zcGVjaWZpYyByYW5kb20gZm9yZXN0KSBmYXJlPwoKYGBge3IsIGVjaG89VFJVRX0KCmJpX2NzcmYgPC0gY3NyZihzdHlsZV9jb2xsYXBzZWQgfiAuLCB0cmFpbmluZ19kYXRhID0gYmlfdHJhaW4sIHRlc3RfZGF0YSA9IGJpX3Rlc3QsCiAgICAgICAgICAgICAgICBwYXJhbXMxID0gbGlzdChudW0udHJlZXMgPSA1LCBtdHJ5ID0gNCksCiAgICAgICAgICAgICAgICBwYXJhbXMyID0gbGlzdChudW0udHJlZXMgPSAyKSkKCmNzcmZfYWNjIDwtIHBvc3RSZXNhbXBsZShiaV9jc3JmLCBiaV90ZXN0JHN0eWxlX2NvbGxhcHNlZCkKCmNzcmZfYWNjCmBgYAoKCiFbXSguL3BvdXIuanBnKQoKCgojIyMgRmluYWwgVGhvdWdodHMKCgoqU3R5bGUgZmlyc3QsIGZvcmdpdmVuZXNzIGxhdGVyPyoKCiogT25lIHJlYXNvbiAgc2VlbXMgdGhhdCBiZWVycyBhcmUgZ2VuZXJhbGx5IGJyZXdlZCB3aXRoIHN0eWxlIGluIG1pbmQgZmlyc3QgKCJsZXQncyBtYWtlIGEgcGFsZSBhbGUiKSByYXRoZXIgdGhhbiBkZWNpZGluZyB0aGUgYmVlcidzIHN0eWxlIGFmdGVyIGRldGVybWluaW5nIGl0cyBjaGFyYWN0ZXJpc3RpY3MgYW5kIGlkaW9zeW5jcmFzaWVzIAogICAgKiBFdmVuIGlmIHRoZSBiZWVyIHR1cm5zIG91dCBtb3JlIGxpa2UgYSBzb3VyLCBhbmQgaW4gYSBibGluZCB0YXN0ZSB0ZXN0IG1pZ2h0IGJlIGNsYXNzaWZpZWQgYXMgYSBzb3VyIG1vcmUgb2Z0ZW4gdGhhbiBhIHBhbGUgYWxlLCBpdCBzdGlsbCBnZXRzIHRoZSBsYWJlbCBwYWxlIGFsZQogICAgKiBUaGlzIG1ha2VzIHRoZSBzdHlsZSBkZWZpbml0aW9ucyBicm9hZGVyIGFuZCBoYXJkZXIgdG8gcHJlZGljdAoKCgoqRnV0dXJlIERpcmVjdGlvbnMqCgoqIEluY29ycG9yYXRlIGZsYXZvciBwcm9maWxlcyBmb3IgYmVlcnMgc291cmNlZC9zY3JhcGVkIGZyb20gc29tZXdoZXJlCiogSW1wbGVtZW50IGEgR0FOIHRvIGNvbWUgdXAgd2l0aCBiZWVyIG5hbWVzCiogTW9yZSBvbiB0aGUgaG9wcyBkZWVwIGRpdmU6IHdoaWNoIGhvcHMgYXJlIHVzZWQgbW9zdCBvZnRlbiBpbiB3aGljaCBzdHlsZXM/CgoKCgoK